Conversation
|
Caution Review failedThe pull request is closed. Warning There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure. 🔧 eslint
apps/web/api/items.tsOops! Something went wrong! :( ESLint: 8.57.1 ESLint couldn't find the config "@repo/eslint-config/next.js" to extend from. Please check that the name of the config is correct. The config "@repo/eslint-config/next.js" was referenced from the config file in "/apps/web/.eslintrc.js". If you still have problems, please stop by https://eslint.org/chat/help to chat with the team. Walkthrough이 풀 리퀘스트는 회의실 예약 시스템의 프론트엔드 구현을 크게 개선하고 있습니다. 주요 변경 사항은 예약 관리를 위한 새로운 API 함수, 커스텀 훅, 컴포넌트 추가, 그리고 기존 컴포넌트의 타입 및 로직 업데이트를 포함합니다. 이러한 변경은 애플리케이션의 예약 기능을 더욱 유연하고 사용자 친화적으로 만들어줍니다. Changes
Possibly related issues
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (8)
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 36
🧹 Outside diff range and nitpick comments (75)
apps/web/app/meetings/page.tsx (1)
4-4: 성능 최적화 제안
MeetingRoomSchedule컴포넌트가 복잡한 데이터 fetching과 상태 관리를 포함하고 있을 것으로 예상됩니다. 필요한 경우 다음 최적화를 고려해보세요:
- React.memo를 사용한 불필요한 리렌더링 방지
- 데이터 fetching을 위한 suspense 경계 설정
- 에러 바운더리 구현
apps/web/app/utils/formatDate.ts (1)
1-3: 기본 구현이 깔끔하고 명확합니다.날짜 형식이 ISO 8601 표준을 따르고 있으며, padStart를 사용한 구현이 적절합니다.
입력값 유효성 검사 추가 권장
날짜 값의 유효성을 검증하는 로직이 필요합니다:
- 월: 1-12
- 일: 1-31 (월별로 다름)
- 연도: 적절한 범위 설정
다음과 같은 개선된 구현을 제안드립니다:
+function isValidDate(date: { year: number; month: number; day: number }): boolean { + const { year, month, day } = date; + const d = new Date(year, month - 1, day); + return d.getFullYear() === year && d.getMonth() === month - 1 && d.getDate() === day; +} + export function formatDate(date: { year: number; month: number; day: number }): string { + if (!date?.year || !date?.month || !date?.day) { + throw new Error('날짜 정보가 올바르지 않습니다.'); + } + if (!isValidDate(date)) { + throw new Error('유효하지 않은 날짜입니다.'); + } return `${String(date.year)}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`; }타입 정의 개선 제안
더 구체적인 타입 제한을 위해 다음과 같은 타입 정의를 고려해보세요:
type DateInput = { year: number; month: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; day: number; };apps/web/app/utils/timeToMinutes.ts (1)
3-7: JSDoc 문서를 개선해주세요.현재 문서에 다음과 같은 중요한 정보들이 누락되어 있습니다:
- 입력값이 문자열일 경우 예상되는 ISO 형식
- 반환되는 시간이 자정(00:00) 기준 총 경과 분임을 명시
- 예제 코드
다음과 같이 개선하는 것을 제안합니다:
/** * 주어진 시간(Date 객체 또는 ISO 문자열)을 분 단위로 변환합니다. * @param time Date 객체 또는 ISO 문자열 * @returns 분 단위의 숫자 + * @example + * timeToMinutes(new Date('2024-01-01T14:30:00')) // 870 (14시 30분 = 14 * 60 + 30) + * timeToMinutes('2024-01-01T09:15:00') // 555 (9시 15분 = 9 * 60 + 15) */apps/web/app/meetings/_components/Reservation/ReservationForm/DeleteButton.tsx (2)
5-11: 컴포넌트 구현이 깔끔하고 타입이 잘 정의되어 있습니다.컴포넌트가 간단하고 목적에 맞게 잘 구현되어 있습니다. 하지만 접근성 향상을 위해 몇 가지 개선사항을 제안드립니다.
다음과 같이 접근성 속성을 추가하는 것을 고려해보세요:
<Button variant="Primary" className="mt-20 h-48 w-full" onClick={onDelete} + aria-label="예약 삭제" + role="button" > {BUTTON_TEXT.delete} </Button>
7-7: 스타일링 관련 제안현재 하드코딩된 높이와 마진 값을 theme 시스템이나 공통 스타일 상수로 분리하는 것을 고려해보세요.
apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeLabel.tsx (1)
6-16: 하드코딩된 마진값 개선이 필요합니다.현재 구현은 대체로 잘 되어있지만, 몇 가지 개선사항을 제안드립니다:
-ml-16와 같은 하드코딩된 마진값은 유지보수를 어렵게 만들 수 있습니다.- 시간 레이블의 위치 조정이 화면 크기에 따라 더 유동적으로 대응할 필요가 있습니다.
다음과 같이 개선해보시는 것은 어떨까요?:
- className="text-xs-semibold text-custom-black absolute -bottom-24 z-30 -ml-16 rounded bg-none md:-bottom-20 md:block" + className="text-xs-semibold text-custom-black absolute -bottom-24 z-30 transform -translate-x-1/2 rounded bg-none md:-bottom-20 md:block"이렇게 하면:
- transform과 translate를 사용하여 더 유연한 중앙 정렬이 가능합니다
- 반응형 레이아웃에서도 일관된 동작을 기대할 수 있습니다
apps/web/app/meetings/_components/skeleton/RoomNameSkeleton.tsx (1)
7-7: Tailwind 클래스 구조화 제안현재 긴 className 문자열이 가독성을 저하시킵니다.
다음과 같이 클래스를 논리적 그룹으로 분리하는 것을 제안드립니다:
- <div className="transition-linear md:min-w-128 rounded-8 border-1 text-lg-medium ml-16 inline-block h-48 min-w-80 animate-pulse gap-8 border-gray-200/10 bg-white p-12 text-center md:h-60 md:px-32 md:py-16"> + <div className={` + // Layout + inline-block min-w-80 h-48 ml-16 gap-8 + // Typography + text-lg-medium text-center + // Appearance + rounded-8 border-1 border-gray-200/10 bg-white + // Spacing + p-12 + // Animation + transition-linear animate-pulse + // Responsive + md:min-w-128 md:h-60 md:px-32 md:py-16 + `}>apps/web/app/meetings/_components/skeleton/TimeTextSkeleton.tsx (1)
8-12: 성능 최적화를 위한 제안현재 24개의 엘리먼트를 생성하는 방식은 다음과 같이 개선될 수 있습니다:
- useMemo를 사용하여 배열 생성 최적화
- 반복되는 스타일을 별도의 클래스로 추출
+const HOURS_IN_DAY = 24; +const skeletonBaseClass = "min-w-144 -ml-40 mt-6 flex animate-pulse items-center justify-center"; + function TimeTextSkeleton(): JSX.Element { + const hours = useMemo(() => Array.from({ length: HOURS_IN_DAY }), []); return ( <div className="flex w-full"> - {Array.from({ length: 24 }, (_, index) => ( + {hours.map((_, index) => ( - <div key={`skeleton-${index}`} className="min-w-144 -ml-40 mt-6 flex animate-pulse items-center justify-center"> + <div key={`skeleton-${index}`} className={skeletonBaseClass}> <div className="h-12 w-40 rounded bg-gray-400" /> </div> ))}apps/web/app/meetings/_components/Schedule/RoomName.tsx (1)
13-13: className을 더 간단하게 정리해보세요현재 className이 너무 길어 가독성이 떨어집니다. Tailwind의 @apply 디렉티브를 사용하여 스타일을 분리하는 것을 추천드립니다.
다음과 같이 변경해보세요:
- <div className="transition-linear md:min-w-128 rounded-8 border-1 text-custom-black/80 text-lg-medium hover:bg-custom-black/90 inline-block h-48 min-w-80 gap-8 border-gray-200/10 bg-white p-12 text-center hover:text-white/90 md:h-60 md:px-32 md:py-16"> + <div className="room-name-container">그리고 별도의 CSS 파일에 다음을 추가하세요:
.room-name-container { @apply transition-linear md:min-w-128 rounded-8 border-1 text-custom-black/80; @apply text-lg-medium hover:bg-custom-black/90 inline-block h-48 min-w-80; @apply gap-8 border-gray-200/10 bg-white p-12 text-center; @apply hover:text-white/90 md:h-60 md:px-32 md:py-16; }apps/web/api/users.ts (1)
16-25: 함수 명명 규칙 개선이 필요합니다
getAllUser는 복수형 데이터를 반환하므로getAllUsers로 변경하는 것이 더 명확할 것 같습니다.다음과 같이 수정을 제안드립니다:
-export const getAllUser = async (): Promise<IUser[]> => { +export const getAllUsers = async (): Promise<IUser[]> => {apps/web/app/types/scheduletypes.ts (1)
22-22: 타입 안전성이 개선되었습니다!
string타입에서SelectedRoom | null로 변경함으로써 타입 안전성이 향상되었습니다. 다만, 주석은 인터페이스 정의만으로도 충분히 이해할 수 있으므로 제거하는 것이 좋을 것 같습니다.- selectedRoom: SelectedRoom | null; // name과 _id를 포함 + selectedRoom: SelectedRoom | null;apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleTooltip.tsx (1)
10-10: children prop 사용이 올바르게 구현되었습니다.props 구조 분해와 children 렌더링이 React의 관행에 맞게 잘 구현되었습니다. 하지만 children이 없을 때의 처리를 고려해볼 필요가 있습니다.
다음과 같이 기본값을 제공하는 것을 고려해보세요:
- const { children } = props; + const { children = '내용이 없습니다.' } = props;Also applies to: 14-14
apps/web/app/meetings/_components/Schedule/ScheduleTable/index.tsx (2)
7-11: 인터페이스 정의가 명확하게 되어있습니다.
ScheduleTableProps인터페이스가 필요한 모든 속성을 포함하고 있으며, 각 속성의 타입이 명확하게 정의되어 있습니다.다만, 추가적인 타입 안전성을 위해 다음과 같은 개선을 고려해보시면 좋을 것 같습니다:
interface ScheduleTableProps { rooms: TBaseItem[]; meetingsData: IReservation[]; - selectedDate: string; + selectedDate: string; // ISO 8601 format (YYYY-MM-DD) }
14-19: 컴포넌트 구현이 적절하게 되어있습니다.props 구조 분해와 하위 컴포넌트로의 데이터 전달이 일관성 있게 구현되어 있습니다. 모바일과 데스크톱 버전 모두에 동일한 데이터가 전달되고 있습니다.
하지만 성능 최적화를 위해 다음과 같은 개선을 제안드립니다:
-export default function ScheduleTable(props: ScheduleTableProps): JSX.Element { +const ScheduleTable = memo(function ScheduleTable(props: ScheduleTableProps): JSX.Element { const { rooms, meetingsData, selectedDate } = props; return ( <div className="overflow-hidden"> <ScheduleTableMobile rooms={rooms} meetingsData={meetingsData} selectedDate={selectedDate} /> <ScheduleTableDesktop rooms={rooms} meetingsData={meetingsData} selectedDate={selectedDate} /> </div> ); -} +}); + +export default ScheduleTable;불필요한 리렌더링을 방지하기 위해
memo를 사용하는 것을 고려해보세요. 특히rooms와meetingsData배열이 자주 변경되지 않는 경우 유용할 수 있습니다.apps/web/app/meetings/_components/skeleton/ScheduleSlotSkeleton.tsx (2)
8-9: 하드코딩된 값에 대한 설명이 필요합니다
length: 7와width: 72 * 49와 같은 하드코딩된 값들의 의미를 주석으로 설명하면 코드의 가독성이 향상될 것 같습니다.다음과 같이 주석을 추가하는 것을 제안합니다:
+ // 7: 일주일의 요일 수 {Array.from({ length: 7 }, (_, index) => ( + // 72px: 기본 셀 너비, 49: 시간대 수 (예: 오전 9시 ~ 오후 10시) <div className="relative mb-36" style={{ width: 72 * 49, height: 80 }} key={`Slot-${index}`}>
7-16: 접근성 개선이 필요합니다스켈레톤 UI의 접근성을 개선하기 위해 aria 속성을 추가하는 것이 좋습니다.
다음과 같이 수정하는 것을 제안합니다:
- <div className="relative h-full w-full"> + <div + className="relative h-full w-full" + role="status" + aria-label="일정 로딩 중" + > {Array.from({ length: 7 }, (_, index) => ( <div className="relative mb-36" style={{ width: 72 * 49, height: 80 }} key={`Slot-${index}`}> <div className="absolute inset-0 flex items-center justify-center"> - <div className="absolute top-1/2 h-16 w-full -translate-y-1/2 transform bg-gray-400" /> + <div + className="absolute top-1/2 h-16 w-full -translate-y-1/2 transform bg-gray-400" + aria-hidden="true" + /> </div> <div className="border-gray-10 md:h-110 absolute left-0 top-0 h-full border-l-2" /> </div> ))} + <span className="sr-only">로딩중...</span> </div>apps/web/app/meetings/_components/Reservation/ReservationForm/NotesInput.tsx (1)
17-17: 불필요한 삼항 연산자 사용
error={error ? error : undefined}는error={error}로 단순화할 수 있습니다. TypeScript는 자동으로 undefined를 처리합니다.apps/web/app/meetings/_components/Reservation/ReservationModal.tsx (2)
10-13: 인터페이스 확장이 잘 되었습니다만, 문서화를 개선하면 좋겠습니다.새로운 프로퍼티들이 잘 추가되었습니다. 하지만 각 프로퍼티의 목적과 사용법을 더 자세히 설명하는 JSDoc 주석을 추가하면 좋을 것 같습니다.
다음과 같이 JSDoc 주석을 추가하는 것을 제안드립니다:
interface ReservationModalProps { isOpen: boolean; onClose: () => void; onConfirm: () => void; - title: string; // 추가 - content: ReactNode; // 추가 - cancelButtonName: string; // 추가 - confirmButtonName: string; // 추가 + /** 모달의 제목 */ + title: string; + /** 모달의 내용 (React 컴포넌트 지원) */ + content: ReactNode; + /** 취소 버튼에 표시될 텍스트 */ + cancelButtonName: string; + /** 확인 버튼에 표시될 텍스트 */ + confirmButtonName: string; }
32-35: AlertModal에 프로퍼티 전달이 잘 되었습니다만, 개선의 여지가 있습니다.프로퍼티 전달은 정확하게 되었지만, 불필요한 래핑을 제거하여 코드를 더 간결하게 만들 수 있습니다.
onConfirm 핸들러를 다음과 같이 단순화하는 것을 제안드립니다:
<AlertModal isOpen={isOpen} onClose={onClose} - onConfirm={() => { - onConfirm(); - }} + onConfirm={onConfirm} title={title} content={content} cancelButtonName={cancelButtonName} confirmButtonName={confirmButtonName} />apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx (2)
14-18: 성능 최적화를 위한 제안
useCurrentTimePosition훅의 반환값이 자주 업데이트될 수 있으므로, 불필요한 리렌더링을 방지하기 위해useMemo나React.memo의 사용을 고려해보세요.+const MemoizedCurrentTimeLabel = React.memo(CurrentTimeLabel); export default function CurrentTimeIndicator(props: CurrentTimeIndicatorProps): JSX.Element { const { slotWidth, startHour, endHour } = props; const { currentPosition, currentTime } = useCurrentTimePosition({ slotWidth, startHour, endHour, });
20-22: null 케이스 처리 개선 제안빈
div대신 의미있는 컴포넌트나null을 반환하는 것이 더 명확할 것 같습니다.- if (currentPosition === null) { - return <div />; - } + if (currentPosition === null) { + return null; + }apps/web/app/constants/reservationConstants.ts (3)
1-2: ESLint 규칙 비활성화에 대한 설명이 필요합니다ESLint 규칙을 비활성화하는 이유를 주석으로 설명해주시면 좋겠습니다.
-/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/naming-convention */ +// 상수 네이밍에 대한 ESLint 규칙을 비활성화합니다. +// 이는 NOTIFICATION_MESSAGES와 같은 대문자 상수명을 허용하기 위함입니다.
8-12: 알림 메시지 상수가 잘 정의되었습니다알림 메시지가 일관성 있게 정의되어 있습니다. 다만, 실패 케이스에 대한 메시지도 추가하면 좋을 것 같습니다.
export const NOTIFICATION_MESSAGES = { create: "회의실이 예약되었습니다.", update: "예약이 수정되었습니다.", delete: "예약이 삭제되었습니다.", + error: { + create: "예약 생성 중 오류가 발생했습니다.", + update: "예약 수정 중 오류가 발생했습니다.", + delete: "예약 삭제 중 오류가 발생했습니다.", + }, };
14-16: 쿼리 키 구조 검토가 필요합니다쿼리 키가 배열 형태로 반환되고 있는데, 타입 안정성을 위해 as const 대신 더 명시적인 타입을 사용하는 것이 좋을 것 같습니다.
+type MeetingsQueryKey = readonly ["meetings", string, string]; + export const QUERY_KEYS = { - meetings: (date: string, type: string) => ["meetings", date, type] as const, + meetings: (date: string, type: string): MeetingsQueryKey => ["meetings", date, type], };apps/web/app/meetings/_components/skeleton/index.tsx (1)
12-16: 상수로 분리하여 코드의 의도를 명확히 표현해주세요.매직 넘버 7을 의미 있는 상수로 분리하면 코드의 가독성이 향상될 것 같습니다.
다음과 같이 변경을 제안드립니다:
+const TOTAL_ROOM_COUNT = 7; function MeetingsSkeleton(): JSX.Element { return ( // ... - {Array.from({ length: 7 }).map((_, index) => ( + {Array.from({ length: TOTAL_ROOM_COUNT }).map((_, index) => (apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx (2)
6-11: 인터페이스에 JSDoc 문서화를 추가하면 좋을 것 같습니다.각 프로퍼티의 목적과 사용법을 명확히 이해할 수 있도록 JSDoc 문서화를 추가하는 것이 좋겠습니다.
예시:
+/** + * 모바일 예약 시트의 프로퍼티 + */ interface MobileReservationSheetProps { + /** 시트의 열림 상태 */ isOpen: boolean; + /** 시트 닫기 핸들러 */ onClose: () => void; + /** 선택된 시간 */ selectedTime: string; + /** 선택된 예약 정보 */ selectedSchedule?: IReservation | null; + /** 선택된 회의실 정보 */ selectedRoom?: SelectedRoom | null; }
14-31: 컴포넌트 구현이 깔끔하며, 한 가지 개선사항을 제안드립니다.컴포넌트 구현이 전반적으로 잘 되어있습니다. 다만, snapPoints 값을 상수로 분리하면 더 좋을 것 같습니다.
다음과 같이 상수를 분리하는 것을 제안드립니다:
+const SHEET_SNAP_POINTS = [0.8] as const; +const INITIAL_SNAP_INDEX = 0; export default function MobileReservationSheet(props: MobileReservationSheetProps): JSX.Element { const { isOpen, onClose, selectedTime, selectedSchedule, selectedRoom } = props; return ( - <Sheet isOpen={isOpen} onClose={onClose} snapPoints={[0.8]} initialSnap={0} className="!z-50 block md:hidden"> + <Sheet isOpen={isOpen} onClose={onClose} snapPoints={SHEET_SNAP_POINTS} initialSnap={INITIAL_SNAP_INDEX} className="!z-50 block md:hidden">apps/web/components/common/Profile/index.tsx (1)
35-35: 인라인 스타일을 Tailwind CSS 클래스로 변경한 것이 좋습니다!Tailwind CSS 클래스를 사용하여 스타일을 적용한 것은 코드의 일관성과 유지보수성을 향상시킵니다.
접근성 향상을 위해 다음과 같이
alt텍스트를 더 구체적으로 수정하는 것을 제안합니다:- alt="프로필 이미지" + alt={`${name || '사용자'}의 프로필 이미지`}apps/web/app/meetings/_components/Reservation/ReservationSheets.tsx (1)
18-18: 빈 상태 처리를 개선해 주세요.현재
selectedTime또는selectedRoom이 없을 때 빈div를 반환하고 있습니다. 사용자 경험 향상을 위해 의미 있는 빈 상태를 표시하는 것이 좋습니다.다음과 같이 개선해 보세요:
- if (!selectedTime || !selectedRoom) return <div />; + if (!selectedTime || !selectedRoom) { + return ( + <div className="flex items-center justify-center p-4 text-gray-500"> + 예약 정보를 불러올 수 없습니다 + </div> + ); + }apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleItem.tsx (2)
42-42: 툴팁 내용 변경에 대한 검토 필요
schedule.title대신schedule.notes를 표시하도록 변경되었습니다. 다음 사항들을 고려해주세요:
notes가 없는 경우 빈 툴팁이 표시될 수 있습니다- 긴
notes내용에 대한 처리가 필요할 수 있습니다다음과 같은 개선을 제안드립니다:
-{isCurrentUser ? null : <ScheduleTooltip>{schedule.notes}</ScheduleTooltip>} +{isCurrentUser ? null : ( + <ScheduleTooltip> + {schedule.notes || '예약 정보 없음'} + </ScheduleTooltip> +)}
Line range hint
28-33: 클릭 이벤트 처리 로직 개선 필요현재 사용자의 일정만 클릭 가능하도록 되어있는데, 다른 사용자의 일정도 읽기 전용으로 볼 수 있게 하는 것이 좋을 것 같습니다.
다음과 같은 개선을 제안드립니다:
onClick={(e) => { e.stopPropagation(); - if (isCurrentUser) onClick(); + onClick(); // 모든 일정 클릭 가능하도록 수정 }}apps/web/app/_hooks/useSlotReservations.ts (4)
6-13: JSDoc 문서 개선 제안현재 문서화가 잘 되어 있지만, 실제 사용 예시를 추가하면 더욱 유용할 것 같습니다. 또한 반환되는 배열의 각 요소가 null일 때의 의미도 설명하면 좋겠습니다.
다음과 같이 문서를 보완하는 것을 제안합니다:
/** * 예약 데이터를 슬롯 인덱스에 매핑하는 커스텀 훅 * @param schedules 예약 데이터 배열 * @param startHour 시작 시간 * @param endHour 종료 시간 * @param minutesPerSlot 슬롯당 분 단위 * @returns 슬롯별 예약 상태 배열 + * @example + * const slots = useSlotReservations(schedules, 9, 18, 30); + * // slots[0]이 null인 경우: 해당 시간대에 예약이 없음 + * // slots[0]이 IReservation인 경우: 해당 시간대에 예약이 있음 */
21-21: 매직 넘버 상수화 필요코드에서 사용된 숫자 2는 30분 단위 계산을 위한 것으로 보이지만, 이해하기 어려울 수 있습니다. 의미 있는 상수로 추출하는 것이 좋겠습니다.
+const SLOTS_PER_HOUR = 2; // 30분 단위 -const totalSlots = (endHour - startHour) * 2; // 30분 단위 +const totalSlots = (endHour - startHour) * SLOTS_PER_HOUR;
24-36: 예약 시간 중복 처리 로직 개선 필요현재 구현에서는 동일한 시간대에 여러 예약이 있을 경우, 마지막 예약만 저장됩니다. 이는 의도된 동작인지 확인이 필요하며, 중복 예약을 감지하고 처리하는 로직이 필요할 수 있습니다.
중복 예약 처리를 위한 두 가지 접근 방식을 제안합니다:
- 중복 예약 감지 및 경고:
if (slotReservations[i] !== null) { console.warn(`시간 중복 발생: ${schedule.startAt} - ${schedule.endAt}`); }
- 중복 예약 정보 보존:
// IReservation | IReservation[] | null 타입으로 변경 if (slotReservations[i] !== null) { slotReservations[i] = Array.isArray(slotReservations[i]) ? [...slotReservations[i], schedule] : [slotReservations[i], schedule]; }
20-39: 성능 최적화 관련 조언useMemo의 의존성 배열에 포함된 값들의 변경이 빈번할 경우 메모이제이션의 이점이 감소할 수 있습니다. 특히 schedules 배열이 자주 변경되는 경우, 최적화 전략을 재검토해야 할 수 있습니다.
다음과 같은 최적화 전략을 고려해보세요:
- schedules 배열을 안정적으로 유지하기 위해 상위 컴포넌트에서 useMemo 사용
- 필요한 경우 useCallback과 함께 사용하여 콜백 함수의 안정성 보장
- 데이터 구조를 최적화하여 불필요한 재계산 방지
apps/web/app/meetings/_components/MeetingRoomSchedule.tsx (2)
3-11: 임포트 구문 개선 제안임포트 구문을 다음과 같은 순서로 정리하면 코드의 가독성이 향상될 것 같습니다:
- 외부 라이브러리
- 타입 정의
- 내부 유틸리티/상수
- 컴포넌트
-import { type IReservation, type TBaseItem } from "@repo/types"; -import { useQuery } from "@tanstack/react-query"; -import { useDateStore } from "@/app/store/useDateStore"; -import { MEETING_ROOMS_TYPE } from "@/app/constants/meetingRoomsType"; -import { formatDate } from "@/app/utils/formatDate"; -import { getAllItems } from "@/api/items"; -import { getReservationsByTypeAndDate } from "@/api/reservations"; -import ScheduleTable from "./Schedule/ScheduleTable"; -import MeetingsSkeleton from "./skeleton"; +import { useQuery } from "@tanstack/react-query"; + +import { type IReservation, type TBaseItem } from "@repo/types"; + +import { useDateStore } from "@/app/store/useDateStore"; +import { MEETING_ROOMS_TYPE } from "@/app/constants/meetingRoomsType"; +import { formatDate } from "@/app/utils/formatDate"; +import { getAllItems } from "@/api/items"; +import { getReservationsByTypeAndDate } from "@/api/reservations"; + +import ScheduleTable from "./Schedule/ScheduleTable"; +import MeetingsSkeleton from "./skeleton";
28-31: 에러 바운더리 및 Props 타입 검증 추가 필요
- 에러 바운더리를 추가하여 컴포넌트 크래시를 방지해야 합니다.
ScheduleTable컴포넌트의 props 타입을 명시적으로 정의해야 합니다.+interface ScheduleTableProps { + rooms: TBaseItem[]; + meetingsData: IReservation[]; + selectedDate: string; +} + +// ErrorBoundary 컴포넌트 추가 필요 if (meetingsIsLoading || roomsIsLoading) return <MeetingsSkeleton />; return <ScheduleTable rooms={roomsData} meetingsData={meetingsData} selectedDate={formattedDate} />;apps/web/app/types/ReservationFormTypes.ts (4)
6-13: 인터페이스 문서화 개선 제안각 프로퍼티의 목적과 사용 사례를 명확히 하기 위해 JSDoc 문서화를 추가하는 것이 좋겠습니다.
+/** + * 예약 폼 컴포넌트의 프로퍼티 + */ export interface ReservationFormProps { + /** 폼 제출 시 호출되는 함수. 새로운 예약 생성 또는 기존 예약 수정에 사용됨 */ onSubmit: (data: CreateReservationRequest, itemId: string, reservationId?: string) => void; + /** 선택된 시간 (ISO 문자열 형식) */ selectedTime: string; + /** 수정 모드에서 사용되는 기존 예약 정보 */ selectedSchedule?: IReservation | null; + /** 폼 초기화를 트리거하는 값 */ resetTrigger?: number; + /** 선택된 회의실 정보 */ selectedRoom?: SelectedRoom | null; + /** 예약 삭제 시 호출되는 함수 */ onDelete: () => void; }
19-22: selectedRoom 타입 개선 제안회의실 식별자로 string 대신 더 구체적인 타입을 사용하면 타입 안정성이 향상될 것 같습니다.
export interface RoomDropdownProps { - selectedRoom: string; + selectedRoom: SelectedRoom['id']; onSelect: (value: string) => void; }
32-37: 에러 상태 타입 개선 제안isError boolean 대신 구체적인 에러 정보를 포함할 수 있도록 개선하면 좋겠습니다.
export interface AttendeesMultiSelectProps { control: Control<CreateReservationRequest>; allUsersData: IUser[]; isLoading: boolean; - isError: boolean; + error?: { + code: string; + message: string; + }; }
39-41: 삭제 작업 안전성 개선 제안삭제 작업의 안전성을 높이기 위해 확인 단계 관련 프로퍼티를 추가하면 좋겠습니다.
export interface DeleteButtonProps { onDelete: () => void; + requireConfirmation?: boolean; + confirmationMessage?: string; }apps/web/app/meetings/_components/Reservation/ReservationForm/RoomDropdown.tsx (3)
1-9: ESLint 규칙 비활성화 범위 수정 필요파일 전체에 대한
no-nested-ternary규칙 비활성화는 바람직하지 않습니다. 특정 코드 블록에만 적용되도록 수정하는 것이 좋습니다.다음과 같이 수정하는 것을 제안합니다:
-/* eslint-disable no-nested-ternary */그리고 필요한 부분에만 다음과 같이 적용:
// eslint-disable-next-line no-nested-ternary {isLoading ? (...) : isError ? (...) : (...)}
10-19: Query 설정 개선 필요현재 구현은 기본적인 기능은 제공하지만, 다음 사항들을 고려해보시기 바랍니다:
- 에러 재시도 설정
- 캐시 시간 설정
- 스태일 타임 설정
다음과 같이 수정하는 것을 제안합니다:
useQuery<TBaseItem[]>({ queryKey: ["Rooms", MEETING_ROOMS_TYPE], queryFn: () => getAllItems({ itemType: MEETING_ROOMS_TYPE }), + retry: 2, + staleTime: 5 * 60 * 1000, // 5분 + cacheTime: 30 * 60 * 1000, // 30분 });
30-30: 접근성 개선 필요스크롤 가능한 드롭다운 메뉴의 접근성을 개선해야 합니다.
다음과 같이 ARIA 속성을 추가하는 것을 제안합니다:
-<Dropdown.Wrapper className="max-h-160 md:max-h-300 no-scrollbar overflow-y-auto"> +<Dropdown.Wrapper + className="max-h-160 md:max-h-300 no-scrollbar overflow-y-auto" + role="listbox" + aria-label="회의실 목록">apps/web/app/_hooks/useCurrentTimePosition.ts (1)
14-20: 초기 상태 설정이 잘 되어있으나, 성능 최적화를 위한 제안이 있습니다.
useState초기값을 계산하는 함수를 추가하여 첫 렌더링 시 현재 시간 위치를 즉시 계산하는 것이 좋습니다.다음과 같이 수정하는 것을 제안합니다:
- const [currentPosition, setCurrentPosition] = useState<number | null>(null); - const [currentTime, setCurrentTime] = useState<string>(""); + const [currentPosition, setCurrentPosition] = useState<number | null>(() => { + const now = new Date(); + const currentMinutes = now.getHours() * 60 + now.getMinutes(); + if (currentMinutes < startHour * 60 || currentMinutes >= endHour * 60) { + return null; + } + const relativeMinutes = currentMinutes - startHour * 60; + const totalMinutes = (endHour - startHour) * 60; + const scheduleWidth = slotWidth * (endHour - startHour) * 2; + return (relativeMinutes / totalMinutes) * scheduleWidth; + }); + const [currentTime, setCurrentTime] = useState<string>(() => { + const now = new Date(); + return `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`; + });apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlots.tsx (3)
5-10: 인터페이스에 JSDoc 문서를 추가하는 것이 좋습니다각 프로퍼티의 목적과 사용법을 명확하게 설명하는 문서를 추가하면 컴포넌트의 재사용성이 향상될 것 같습니다.
+/** + * ScheduleSlots 컴포넌트의 프로퍼티 + */ interface ScheduleSlotsProps { + /** 각 시간대별 예약 정보 배열 */ slotReservations: (IReservation | null)[]; + /** 각 슬롯의 너비 (픽셀) */ slotWidth: number; + /** 각 슬롯의 높이 (픽셀) */ slotHeight: number; + /** 슬롯 클릭 시 호출되는 콜백 함수 */ onSlotClick: (index: number, schedule?: IReservation) => void; }
12-15: 성능 최적화를 위한 메모이제이션 검토가 필요합니다컴포넌트가 자주 리렌더링될 수 있으므로,
useMemo와useCallback을 활용하여 최적화하는 것이 좋습니다.+import { useMemo, useCallback } from "react"; export default function ScheduleSlots(props: ScheduleSlotsProps): JSX.Element { const { slotReservations, slotWidth, slotHeight, onSlotClick } = props; - const slots: JSX.Element[] = []; + const handleSlotClick = useCallback((index: number, schedule?: IReservation) => { + onSlotClick(index, schedule); + }, [onSlotClick]); const totalSlots = slotReservations.length;
63-64: 에러 바운더리 추가를 고려해보세요예약 데이터 처리 중 발생할 수 있는 예외 상황을 대비하여 에러 바운더리를 추가하면 좋을 것 같습니다.
+import { ErrorBoundary } from '@repo/ui/error-boundary'; export default function ScheduleSlots(props: ScheduleSlotsProps): JSX.Element { // ... - return <>{slots}</>; + return ( + <ErrorBoundary fallback={<div>일정을 불러오는 중 문제가 발생했습니다.</div>}> + {slots} + </ErrorBoundary> + ); }apps/web/api/items.ts (1)
44-61: 아이템 ID 검증과 낙관적 업데이트 구현이 필요합니다.현재 구현에서는
itemId의 유효성 검증이 없으며, 사용자 경험 향상을 위한 낙관적 업데이트가 구현되어 있지 않습니다.다음 사항들을 고려해보세요:
itemId형식 검증- 낙관적 업데이트 구현으로 UI 응답성 향상
- 업데이트 실패 시 롤백 처리
예시 구현:
// utils/validation.ts에 추가 export const validateItemId = (itemId: string): boolean => { return /^[0-9a-fA-F]{24}$/.test(itemId); // MongoDB ObjectId 형식 검증 예시 }; // items.ts 수정 export const updateItem = async (params: UpdateItemParams): Promise<TBaseItem> => { const { itemId, itemData } = params; if (!validateItemId(itemId)) { throw new Error('유효하지 않은 아이템 ID입니다.'); } // 낙관적 업데이트를 위한 이전 상태 저장 const previousState = store.getState().items[itemId]; try { // 낙관적 업데이트 적용 store.dispatch(updateItemOptimistic({ itemId, itemData })); const { data } = await axiosRequester<TBaseItem>({...}); return data; } catch (error) { // 실패 시 이전 상태로 롤백 store.dispatch(revertItemUpdate({ itemId, previousState })); throw error; } };apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx (2)
18-24: 사용자 정보 접근 시 타입 안전성 개선이 필요합니다.
useAuthStore에서 가져온 사용자 정보가 없을 수 있으므로, 이에 대한 처리가 필요합니다.다음과 같이 초기 로딩 상태 처리를 추가하는 것을 고려해보세요:
- const user = useAuthStore((state) => state.user); + const user = useAuthStore((state) => state.user); + const isLoading = useAuthStore((state) => state.isLoading); + + if (isLoading) { + return <div>로딩 중...</div>; + }
36-53: 스타일링 관리 방식 개선이 필요합니다.현재 인라인 스타일과 문자열 연결을 통한 클래스 관리는 유지보수가 어려울 수 있습니다.
다음과 같이 tailwind 클래스를 좀 더 체계적으로 관리하는 것을 추천드립니다:
- className={`hover:bg-gray-60 relative cursor-pointer ${isClicked ? "bg-purple-100" : ""} ${ - isUserReservation ? "hover:bg-purple-100" : "" - }`} + className={clsx( + 'relative cursor-pointer', + 'hover:bg-gray-60', + isClicked && 'bg-purple-100', + isUserReservation && 'hover:bg-purple-100' + )}또한 예약된 슬롯의 스타일을 상수로 분리하여 관리하는 것이 좋습니다:
const RESERVED_SLOT_STYLES = { height: '4rem', backgroundColor: 'var(--purple-400)' } as const;apps/web/app/utils/validateTime.ts (6)
9-20: 인터페이스에 JSDoc 문서를 추가하는 것이 좋습니다.각 매개변수의 목적과 사용법을 명확하게 설명하는 문서를 추가하면 코드의 가독성과 유지보수성이 향상될 것 같습니다.
예시:
+/** + * 예약 종료 시간 검증을 위한 매개변수 인터페이스 + * @param endAt - 예약 종료 시간 (HH:mm 형식) + * @param getValues - form 값을 가져오는 함수 + * @param selectedMeetingRoom - 선택된 회의실 정보 + * @param selectedDate - 선택된 날짜 정보 + * @param meetingsData - 기존 예약 데이터 목록 + * @param selectedReservationId - 수정 중인 예약 ID (선택사항) + */ interface ValidateTimeParams {
22-29: 함수에 JSDoc 문서를 추가하는 것이 좋습니다.반환 값의 의미와 함수의 목적을 명확하게 설명하는 문서가 필요합니다.
+/** + * 예약 종료 시간의 유효성을 검사하는 함수 + * @returns true if valid, error message string if invalid + */ export function validateEndAt({
36-43: 시간 파싱 로직이 견고해 보입니다.시간 차이 검증이 잘 구현되어 있습니다.
parse함수 사용 시 발생할 수 있는 예외 처리를 추가하면 더 좋을 것 같습니다.+try { const start = parse(startAtValue, "HH:mm", new Date()); const end = parse(endAt, "HH:mm", new Date()); +} catch (error) { + return ERROR_MESSAGES.invalidTimeFormat; +}
45-47: 조기 반환 조건에 대한 설명이 필요합니다.회의실이 선택되지 않은 경우 true를 반환하는 이유를 주석으로 설명하면 좋을 것 같습니다.
+// 회의실이 선택되지 않은 경우 중복 검사를 수행하지 않고 유효한 것으로 처리 if (!selectedMeetingRoom?._id) { return true; }
51-54: 날짜 문자열 생성 로직을 유틸리티 함수로 분리하는 것이 좋습니다.날짜 문자열 생성 로직이 반복되므로, 재사용 가능한 유틸리티 함수로 분리하면 코드 중복을 줄일 수 있습니다.
+const createDateString = (year: number, month: number, day: number, time: string) => + `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${time}:00`; -const newStart = new Date( - `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${startAtValue}:00`, -); +const newStart = new Date(createDateString(year, month, day, startAtValue));
61-68: 아이템 ID 추출 로직을 별도 함수로 분리하면 좋을 것 같습니다.복잡한 타입 체크 로직을 별도의 유틸리티 함수로 분리하면 코드의 가독성이 향상될 것 같습니다.
+const getItemId = (item: string | { _id: string } | unknown): string | null => { + if (typeof item === "string") return item; + if (typeof item === "object" && item && "_id" in item) return item._id; + return null; +}; -let itemId: string; -if (typeof reservation.item === "string") { - itemId = reservation.item; -} else if ("_id" in reservation.item) { - itemId = reservation.item._id; -} else { - return false; -} +const itemId = getItemId(reservation.item); +if (!itemId) return false;apps/web/app/meetings/_components/Reservation/ReservationForm/AttendeesMultiSelect.tsx (4)
1-8: 임포트 구문 개선 제안임포트 구문을 다음과 같이 그룹화하면 가독성이 향상될 것 같습니다:
- 외부 라이브러리
- UI 컴포넌트
- 타입
- 상수
+ // External libraries import { Controller } from "react-hook-form"; + + // UI Components import MultiSelectDropdown from "@ui/src/components/common/Dropdown/MultiSelectDropdown"; import { Badge } from "@ui/index"; import Profile from "@/components/common/Profile"; + + // Types import { type IUser } from "@repo/types"; import { type AttendeesMultiSelectProps } from "@/app/types/ReservationFormTypes"; + + // Constants import { ERROR_MESSAGES, FORM_LABELS } from "@/app/constants/reservationFormConstants";
29-39: 성능 및 접근성 개선 제안
- 성능 최적화:
allUsersData.find()호출이 매 렌더링마다 발생합니다. 선택된 사용자 정보를 미리 매핑해두면 좋을 것 같습니다.
- 접근성 개선:
- 추가 참석자 수를 나타내는 텍스트에 aria-label을 추가하면 좋을 것 같습니다.
- {field.value.slice(0, 3).map((name) => { - const matchingUser = allUsersData.find((user) => user.name === name); + const selectedUsers = field.value.slice(0, 3).map((name) => + allUsersData.find((user) => user.name === name) + ).filter(Boolean); + + {selectedUsers.map((matchingUser) => { return matchingUser ? ( <Badge key={matchingUser._id} color="purple" shape="round" colorApplyTo="font"> {matchingUser.name} </Badge> ) : null; })} {field.value.length > 3 && ( - <span className="text-purple-30 text-xs-semibold mx-10">+{field.value.length - 3}명</span> + <span + className="text-purple-30 text-xs-semibold mx-10" + aria-label={`외 ${field.value.length - 3}명의 참석자`} + > + +{field.value.length - 3}명 + </span> )}
45-48: 로딩 및 에러 상태 UI 개선 제안현재 상태 메시지들이 단순한 div로 표시되어 있습니다. 사용자 경험 향상을 위해 다음과 같은 개선을 제안드립니다:
- 로딩 상태에 스켈레톤 UI 추가
- 에러 상태에 재시도 버튼 추가
- 빈 상태에 적절한 아이콘 추가
- {isLoading ? <div>사용자 데이터 로딩 중...</div> : null} - {isError && !isLoading ? <div>{ERROR_MESSAGES.userFetchError}</div> : null} - {!isLoading && !isError && allUsersData.length === 0 && <div>참여자가 없습니다.</div>} + {isLoading ? ( + <div className="flex items-center space-x-2 p-4"> + <div className="animate-pulse h-27 w-27 rounded-full bg-gray-200" /> + <div className="animate-pulse h-4 w-32 bg-gray-200 rounded" /> + </div> + ) : null} + {isError && !isLoading ? ( + <div className="flex flex-col items-center p-4 text-red-500"> + <p>{ERROR_MESSAGES.userFetchError}</p> + <button + onClick={onRetry} + className="mt-2 text-sm text-purple-500 hover:text-purple-600" + > + 다시 시도 + </button> + </div> + ) : null} + {!isLoading && !isError && allUsersData.length === 0 && ( + <div className="flex flex-col items-center p-4 text-gray-500"> + <UserIcon className="w-6 h-6 mb-2" /> + <p>참여자가 없습니다.</p> + </div> + )}
63-70: 팀 태그 스타일링 및 접근성 개선 제안팀 태그의 스타일링과 접근성을 개선하면 좋을 것 같습니다:
- 태그 컴포넌트 분리
- 색상 변수 사용
- 접근성 속성 추가
- {user.teams.map((team: string) => ( - <div - key={team} - className="text-xxs-medium flex h-12 items-center justify-center rounded-lg bg-purple-100 p-6 py-10 text-purple-300" - > - {team} - </div> - ))} + {user.teams.map((team: string) => ( + <span + key={team} + role="badge" + aria-label={`${team} 팀`} + className="text-xxs-medium inline-flex h-12 items-center justify-center rounded-lg bg-purple-100 px-6 py-2 text-purple-300" + > + {team} + </span> + ))}apps/web/app/meetings/_components/Reservation/ReservationForm/TimeSelectors.tsx (2)
16-22: 에러 메시지 처리 최적화 제안현재 에러 메시지를 배열에 push하는 방식은 불필요한 배열 조작을 발생시킵니다.
다음과 같이 map을 사용하여 더 선언적으로 처리하는 것을 추천드립니다:
- const errorMessages = []; - if (errors.startAt) { - errorMessages.push(errors.startAt.message); - } - if (errors.endAt) { - errorMessages.push(errors.endAt.message); - } + const errorMessages = ['startAt', 'endAt'] + .map(field => errors[field]?.message) + .filter(Boolean);
61-64: 검증 로직 개선 제안현재 검증 로직이 단순한 필수 값 체크와
validateEndAt함수에만 의존하고 있습니다.시작 시간과의 관계를 더 명확하게 검증하는 것을 추천드립니다:
rules={{ required: ERROR_MESSAGES.timeRequired, - validate: validateEndAt, + validate: { + isAfterStart: validateEndAt, + isDurationValid: (value, formValues) => { + // 예약 시간이 최소 30분에서 최대 4시간 사이인지 확인 + return true || ERROR_MESSAGES.invalidDuration; + } + }, }}apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx (3)
1-3: ESLint 설정 개선이 필요합니다.전역 ESLint 비활성화보다는 특정 라인에 대해서만 규칙을 비활성화하는 것이 좋습니다.
다음과 같이 수정하는 것을 추천드립니다:
-/* eslint-disable @typescript-eslint/no-shadow */ -/* eslint-disable @typescript-eslint/no-unnecessary-condition */ - +// ESLint 규칙은 필요한 특정 라인에만 적용하세요
25-30: 인터페이스와 상태 타입 문서화가 필요합니다.Props 인터페이스와 폼 데이터 타입에 대한 JSDoc 문서화가 누락되었습니다. 또한 폼 데이터의 타입 안전성을 개선할 수 있습니다.
다음과 같이 개선하는 것을 추천드립니다:
/** * 예약 시트 컨텐츠 컴포넌트의 props 인터페이스 * @property onClose - 시트를 닫는 콜백 함수 * @property selectedTime - 선택된 시간 * @property selectedSchedule - 선택된 일정 (수정 모드에서 사용) * @property selectedRoom - 선택된 회의실 */ interface ReservationSheetContentProps { onClose: () => void; selectedTime: string; selectedSchedule?: IReservation | null; selectedRoom?: SelectedRoom | null; } type FormDataType = { data: CreateReservationRequest; itemId: string; reservationId?: string; } | null;Also applies to: 43-47
105-111: 모달 텍스트 로직 단순화가 필요합니다.모달 텍스트 설정 로직이 반복되어 있어 단순화가 가능합니다.
다음과 같이 개선하는 것을 추천드립니다:
const getModalConfig = () => { const mode = isEditMode ? 'edit' : modalType; return { title: MODAL_TEXT.titles[mode], content: MODAL_TEXT.contents[mode], confirmButtonName: MODAL_TEXT.confirmButtonNames[mode], }; }; const { title, content, confirmButtonName } = getModalConfig();packages/ui/src/components/common/Dropdown/MultiSelectDropdown.tsx (1)
159-159: className 적용 방식 개선을 제안드립니다.현재 구현은 작동하지만, cn 유틸리티를 사용할 때 기본 클래스와 커스텀 클래스의 우선순위를 더 명확하게 제어할 수 있습니다.
다음과 같이 수정하는 것을 제안드립니다:
- className, + className ?? '', // 명시적으로 undefined 처리이렇게 하면 className이 undefined일 때의 동작이 더 예측 가능해집니다.
Also applies to: 181-181
apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx (1)
22-24:schedule.item의 타입 일관성 확보 또는 타입 가드 사용 고려코드에서
schedule.item이 문자열 또는 객체일 수 있어 타입 체크를 수행하고 있습니다. 이는 코드의 복잡성을 증가시키고 가독성을 저하시킬 수 있습니다.IReservation인터페이스에서item의 타입을 일관되게 정의하거나, 타입 가드 함수를 사용하여 코드의 가독성을 향상시킬 수 있습니다.예를 들어, 타입 가드 함수를 만들어 사용할 수 있습니다:
function isItemObject(item: string | { _id: string }): item is { _id: string } { return typeof item !== 'string'; }그리고 코드에서는 다음과 같이 적용합니다:
- const scheduleItemId = typeof schedule.item === "string" ? schedule.item : schedule.item._id; + const scheduleItemId = isItemObject(schedule.item) ? schedule.item._id : schedule.item;apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx (1)
31-35: TypeScript 타입 안정성 강화 필요
schedule.item이 문자열 또는 객체일 수 있으므로, 타입 정의를 명확히 하여 타입 안정성을 높이는 것이 좋습니다.
IReservation인터페이스에서item의 타입을 개선하거나, 코드 내부에서 타입 가드를 추가하여 예상치 못한 오류를 방지할 수 있습니다.apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx (1)
30-32: 상수 값을 컴포넌트 외부로 추출하여 재사용성 향상'startHour', 'endHour', 'minutesPerSlot'과 같은 상수 값을 컴포넌트 외부로 추출하거나 설정 파일로 관리하면 다른 컴포넌트에서도 재사용할 수 있고, 유지보수성이 향상됩니다.
apps/web/api/reservations.ts (1)
47-50: 사용하지 않는 인터페이스 제거 필요
CreateReservationParams인터페이스가 정의되어 있으나 코드에서 사용되지 않고 있습니다. 코드를 정리하고 유지보수성을 높이기 위해 불필요한 코드는 제거하는 것이 좋습니다.다음의 diff를 적용하여 인터페이스를 제거할 수 있습니다:
-export interface CreateReservationParams { - itemId: string; - savedReservation: IReservation; -}apps/web/app/meetings/_components/Reservation/ReservationForm.tsx (4)
129-139:useEffect의존성 배열에setValue추가 권장
useEffect훅에서setValue를 사용하고 있으므로, 의존성 배열에setValue를 추가하는 것이 좋습니다. 이는 React 훅의 동작을 예측 가능하게 유지하고, 린트 에러를 방지하는 데 도움이 됩니다.
157-161:endAt검증 로직 개선 필요현재
newEnd가newStart보다 이전이거나 같은 경우에 에러를 설정하고 있습니다. 하지만 이러한 검증은validateEndAtFunction에서 처리하고 있으므로 중복된 로직이 발생할 수 있습니다. 검증 로직을 한 곳에서 관리하여 코드의 일관성과 유지 보수성을 높이는 것이 좋습니다.
192-201: 예약 시간 겹침 검사 로직 최적화 제안현재 예약 시간 겹침을 검사하는 로직이 복잡하게 구성되어 있습니다. 이 로직을 별도의 유틸리티 함수로 추출하여 재사용성과 가독성을 높일 수 있습니다.
257-257: 삭제 버튼 표시 조건 수정 제안현재
isEditMode가true일 때만DeleteButton이 표시됩니다. 하지만 수정 권한이 있는 사용자만 삭제할 수 있도록 추가적인 권한 검사가 필요할 수 있습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (48)
apps/web/api/items.ts(1 hunks)apps/web/api/reservations.ts(1 hunks)apps/web/api/users.ts(1 hunks)apps/web/app/_hooks/useCurrentTimePosition.ts(1 hunks)apps/web/app/_hooks/useSlotReservations.ts(1 hunks)apps/web/app/api/reservation.ts(0 hunks)apps/web/app/constants/meetingRoomsType.ts(1 hunks)apps/web/app/constants/reservationConstants.ts(1 hunks)apps/web/app/constants/reservationFormConstants.ts(1 hunks)apps/web/app/constants/timeOptions.ts(0 hunks)apps/web/app/meetings/_components/MeetingRoomSchedule.tsx(1 hunks)apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx(2 hunks)apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx(1 hunks)apps/web/app/meetings/_components/Reservation/ReservationForm.tsx(1 hunks)apps/web/app/meetings/_components/Reservation/ReservationForm/AttendeesMultiSelect.tsx(1 hunks)apps/web/app/meetings/_components/Reservation/ReservationForm/DeleteButton.tsx(1 hunks)apps/web/app/meetings/_components/Reservation/ReservationForm/NotesInput.tsx(1 hunks)apps/web/app/meetings/_components/Reservation/ReservationForm/RoomDropdown.tsx(1 hunks)apps/web/app/meetings/_components/Reservation/ReservationForm/TimeSelectors.tsx(1 hunks)apps/web/app/meetings/_components/Reservation/ReservationModal.tsx(1 hunks)apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx(1 hunks)apps/web/app/meetings/_components/Reservation/ReservationSheets.tsx(1 hunks)apps/web/app/meetings/_components/Schedule/RoomName.tsx(1 hunks)apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx(2 hunks)apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeLabel.tsx(1 hunks)apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleItem.tsx(2 hunks)apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx(1 hunks)apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlots.tsx(1 hunks)apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleTooltip.tsx(1 hunks)apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx(1 hunks)apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx(1 hunks)apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx(1 hunks)apps/web/app/meetings/_components/Schedule/ScheduleTable/index.tsx(1 hunks)apps/web/app/meetings/_components/main.tsx(0 hunks)apps/web/app/meetings/_components/skeleton/RoomNameSkeleton.tsx(1 hunks)apps/web/app/meetings/_components/skeleton/ScheduleSlotSkeleton.tsx(1 hunks)apps/web/app/meetings/_components/skeleton/TimeTextSkeleton.tsx(1 hunks)apps/web/app/meetings/_components/skeleton/index.tsx(1 hunks)apps/web/app/meetings/page.tsx(1 hunks)apps/web/app/types/ReservationFormTypes.ts(1 hunks)apps/web/app/types/scheduletypes.ts(1 hunks)apps/web/app/utils/formatDate.ts(1 hunks)apps/web/app/utils/timeToMinutes.ts(1 hunks)apps/web/app/utils/validateTime.ts(1 hunks)apps/web/components/common/Profile/index.tsx(1 hunks)apps/web/package.json(1 hunks)packages/ui/src/components/common/Dropdown/MultiSelectDropdown.tsx(2 hunks)packages/ui/src/components/common/Dropdown/index.stories.tsx(1 hunks)
💤 Files with no reviewable changes (3)
- apps/web/app/api/reservation.ts
- apps/web/app/constants/timeOptions.ts
- apps/web/app/meetings/_components/main.tsx
✅ Files skipped from review due to trivial changes (2)
- apps/web/app/constants/meetingRoomsType.ts
- apps/web/app/constants/reservationFormConstants.ts
🧰 Additional context used
📓 Learnings (1)
apps/web/app/meetings/_components/Schedule/RoomName.tsx (1)
Learnt from: bokeeeey
PR: codeit-internship-group-b/codeit-resources#105
File: apps/web/app/meetings/_components/Schedule/RoomName.tsx:7-15
Timestamp: 2024-11-12T09:41:43.501Z
Learning: `apps/web/app/meetings/_components/Schedule/RoomName.tsx` 파일의 `RoomName` 컴포넌트에서는 `name` prop 대신 `children`과 `ReactNode`를 사용하여 컴포넌트의 유연성을 높입니다.
🔇 Additional comments (48)
apps/web/app/meetings/page.tsx (1)
1-1: 컴포넌트 구현이 깔끔하고 명확합니다!
컴포넌트가 단일 책임 원칙을 잘 따르고 있으며, TypeScript 타입 정의도 적절합니다.
다음 스크립트를 실행하여 임포트된 컴포넌트의 존재 여부를 확인하겠습니다:
Also applies to: 3-5
✅ Verification successful
컴포넌트 구현이 정상적으로 확인되었습니다
MeetingRoomSchedule 컴포넌트가 예상대로 구현되어 있으며, 다음과 같은 특징을 가지고 있습니다:
- 클라이언트 컴포넌트로 올바르게 선언되어 있음
- 필요한 의존성들이 적절히 임포트되어 있음
- React Query를 사용한 데이터 페칭 로직이 잘 구현되어 있음
- 로딩 상태에 대한 처리가 적절히 구현되어 있음
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: MeetingRoomSchedule 컴포넌트 파일 존재 여부 확인
# 컴포넌트 파일 검색
fd "MeetingRoomSchedule.tsx" apps/web/app/meetings/_components/
# 컴포넌트 구현 확인
ast-grep --pattern 'export default function MeetingRoomSchedule'
Length of output: 188
Script:
#!/bin/bash
# MeetingRoomSchedule 컴포넌트의 실제 구현 확인
cat apps/web/app/meetings/_components/MeetingRoomSchedule.tsx
Length of output: 1389
apps/web/app/meetings/_components/Reservation/ReservationForm/DeleteButton.tsx (1)
1-3: import 구문이 깔끔하게 정리되어 있습니다.
공통 컴포넌트와 상수, 타입을 적절히 분리하여 가져오고 있습니다.
apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeLabel.tsx (1)
1-4: 인터페이스 정의가 명확하고 깔끔합니다!
타입 정의가 간단명료하며 목적에 맞게 잘 설계되었습니다.
apps/web/app/meetings/_components/skeleton/RoomNameSkeleton.tsx (2)
1-3: 적절한 클라이언트 사이드 렌더링 설정 확인됨
use client 지시문과 필요한 React import가 올바르게 설정되어 있습니다.
13-13: 메모이제이션 사용이 적절합니다
스켈레톤 컴포넌트의 불필요한 리렌더링을 방지하기 위해 memo를 적절히 사용하였습니다.
apps/web/app/meetings/_components/skeleton/TimeTextSkeleton.tsx (2)
1-5: 컴포넌트 선언이 깔끔하고 명확합니다.
타입 안정성을 위해 JSX.Element 반환 타입을 명시한 것이 좋습니다.
17-17: 메모이제이션 사용이 적절합니다.
props가 없는 컴포넌트이므로 memo를 통한 최적화가 효과적입니다.
apps/web/app/meetings/_components/Schedule/RoomName.tsx (1)
3-14: 컴포넌트 유연성이 개선되었습니다! 👍
name prop 대신 children을 사용하도록 변경한 것은 매우 좋은 개선사항입니다. 이를 통해:
- 더 유연한 컨텐츠 렌더링이 가능해졌습니다
- React의 Composition 패턴을 더 잘 따르게 되었습니다
- 이전 PR의 피드백이 잘 반영되었습니다
apps/web/app/types/scheduletypes.ts (1)
29-32: SelectedRoom 인터페이스 구조가 적절합니다.
MongoDB 문서 구조와 일치하는 명확한 인터페이스 설계입니다. _id를 포함함으로써 데이터베이스 연동 시 타입 안전성을 보장할 수 있습니다.
apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleTooltip.tsx (1)
3-6: Props 인터페이스 변경이 적절해 보입니다.
ReactNode 타입의 children prop으로 변경한 것은 컴포넌트의 유연성을 높이는 좋은 접근입니다. 이를 통해 문자열뿐만 아니라 다양한 React 엘리먼트를 툴팁 내용으로 사용할 수 있게 되었습니다.
apps/web/app/meetings/_components/Schedule/ScheduleTable/index.tsx (1)
3-3: 타입 임포트가 적절히 구현되었습니다.
@repo/types에서 필요한 타입들을 명시적으로 임포트하여 타입 안정성을 확보했습니다.
apps/web/app/meetings/_components/skeleton/ScheduleSlotSkeleton.tsx (1)
20-20: 메모이제이션 사용이 적절합니다
memo를 사용하여 불필요한 리렌더링을 방지한 것이 좋습니다.
apps/web/app/meetings/_components/Reservation/ReservationForm/NotesInput.tsx (1)
1-4: 상수 사용 검증 필요
ERROR_MESSAGES와 FORM_LABELS에서 사용하는 상수가 실제로 존재하는지 확인이 필요합니다.
다음 스크립트로 상수 존재 여부를 확인할 수 있습니다:
✅ Verification successful
상수가 올바르게 정의되어 있음을 확인했습니다
apps/web/app/constants/reservationFormConstants.ts 파일에서 ERROR_MESSAGES와 FORM_LABELS 상수가 모두 올바르게 정의되어 있으며, NotesInput 컴포넌트에서 사용하는 meetingTitleRequired와 meetingTitlePlaceholder 값도 각각 다음과 같이 존재합니다:
- ERROR_MESSAGES.meetingTitleRequired: "미팅 제목을 입력해주세요."
- FORM_LABELS.meetingTitlePlaceholder: "미팅 제목을 입력해주세요."
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the existence of required constants
# Check for meeting title related constants
rg "meetingTitleRequired|meetingTitlePlaceholder" "app/constants/reservationFormConstants.ts"
Length of output: 175
Script:
#!/bin/bash
# First, let's find the correct path for the constants file
fd reservationFormConstants
# Then, let's check the content of ERROR_MESSAGES and FORM_LABELS
rg "ERROR_MESSAGES|FORM_LABELS" -A 5
Length of output: 16083
apps/web/app/meetings/_components/Reservation/ReservationModal.tsx (2)
4-4: 타입 임포트가 올바르게 추가되었습니다!
ReactNode 타입을 명시적으로 임포트하여 타입 안정성을 향상시켰습니다.
16-24: 컴포넌트 시그니처가 깔끔하게 확장되었습니다!
새로운 프로퍼티들이 타입 안전하게 추가되었고, 명시적인 반환 타입 선언도 잘 되어있습니다.
apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx (1)
3-4: 커스텀 훅을 활용한 좋은 구조 설계입니다!
시간 관련 로직을 커스텀 훅으로 분리하고 CurrentTimeLabel 컴포넌트를 도입한 것은 코드의 재사용성과 유지보수성을 높이는 좋은 접근입니다.
apps/web/app/constants/reservationConstants.ts (1)
3-6: 열거형 타입이 적절히 정의되었습니다
모달 타입이 명확하게 구분되어 있어 좋습니다.
apps/web/app/meetings/_components/skeleton/index.tsx (3)
1-6: 코드 구조가 깔끔하고 적절합니다!
필요한 컴포넌트들이 잘 분리되어 있고, 스켈레톤 UI의 특성상 index를 key로 사용하는 것이 허용됩니다.
31-31: 성능 최적화가 적절히 구현되었습니다!
memo를 사용하여 불필요한 리렌더링을 방지한 것이 좋습니다. 스켈레톤 UI의 특성상 props 변경이 거의 없을 것이므로 적절한 최적화입니다.
18-18: no-scrollbar 유틸리티 클래스 정의를 확인해주세요.
스크롤바 숨김 처리를 위한 커스텀 클래스가 올바르게 정의되어 있는지 확인이 필요합니다.
✅ Verification successful
no-scrollbar 클래스가 올바르게 정의되어 있습니다
packages/ui/styles/globals.css 파일에서 no-scrollbar 유틸리티 클래스가 다음과 같이 정확하게 정의되어 있음을 확인했습니다:
-ms-overflow-style: none와scrollbar-width: none속성으로 스크롤바를 숨김- webkit 브라우저를 위한
::-webkit-scrollbar처리도 구현되어 있음
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if no-scrollbar class is defined in CSS/Tailwind files
# Search for no-scrollbar class definition
rg -t css -t scss "no-scrollbar"
rg "no-scrollbar" -g "tailwind.config.*"
Length of output: 102
Script:
#!/bin/bash
# Search for no-scrollbar in all CSS files and global styles
fd -e css -x cat {}
fd "global.css" -x cat {}
fd "globals.css" -x cat {}
# Search in any potential style files
rg "no-scrollbar" --type-add 'styles:*.{css,scss,less,pcss,style.*}' -t styles
Length of output: 4348
apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx (1)
1-4: 타입 임포트가 잘 구성되어 있습니다!
필요한 타입들이 적절한 위치에서 임포트되어 있으며, 코드의 타입 안정성을 잘 보장하고 있습니다.
apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx (4)
1-6: 타입 임포트 구조가 개선되었습니다.
필요한 타입들이 명확하게 임포트되어 있으며, 불필요한 임포트가 제거되었습니다.
Line range hint 18-23: 사이드바 상태 관리가 적절히 구현되었습니다.
useEffect를 통한 사이드바 상태 동기화가 잘 구현되어 있습니다. 하지만 의존성 배열에 있는 isSidebarOpen과 closeSidebar가 실제로 변경될 가능성이 있는지 검토가 필요합니다.
다음 스크립트로 useSidebarStore 구현을 확인할 수 있습니다:
✅ Verification successful
사이드바 상태 관리가 적절하게 구현되어 있습니다.
useSidebarStore 구현을 확인한 결과, isSidebarOpen과 closeSidebar는 Zustand 스토어의 상태와 액션으로 적절하게 구현되어 있습니다. closeSidebar 함수는 단순히 상태를 변경하는 액션이므로 안정적이며, isSidebarOpen은 사이드바의 상태를 추적하는 데 필요한 의존성입니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# useSidebarStore 구현 확인
rg -A 10 "export const useSidebarStore"
Length of output: 753
34-39: ReservationSheetContent로의 책임 위임이 잘 이루어졌습니다.
모달 로직이 제거되고 예약 관련 기능이 ReservationSheetContent로 깔끔하게 위임되었습니다. 다만, 다음 사항을 고려해보시면 좋을 것 같습니다:
사이드바가 닫힐 때 ReservationSheetContent의 상태도 초기화되는지 확인이 필요합니다.
다음 스크립트로 상태 초기화 로직을 확인할 수 있습니다:
✅ Verification successful
Let me search for more information about the state management in ReservationSheetContent.
Let me check the complete implementation of ReservationSheetContent to see how it handles state cleanup.
상태 초기화가 적절히 처리되고 있습니다.
ReservationSheetContent 컴포넌트의 상태는 다음과 같이 관리되고 있습니다:
- 모달이 닫힐 때
onClose콜백이 호출되며, 이는 상위 컴포넌트의 사이드바를 닫습니다 - 예약 생성/수정/삭제 작업이 성공적으로 완료되면 모달 상태(
isModalOpen)가 초기화되고 사이드바가 닫힙니다 - 컴포넌트의 모든 상태는 컴포넌트가 언마운트될 때 자동으로 정리됩니다
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# ReservationSheetContent 컴포넌트에서 cleanup 효과 확인
ast-grep --pattern 'useEffect(() => {
return () => {
$$$
}
}, $$$)'
Length of output: 767
Script:
#!/bin/bash
# Search for ReservationSheetContent component implementation
rg -A 10 "ReservationSheetContent" --type tsx
Length of output: 73
Script:
#!/bin/bash
# Search for ReservationSheetContent component implementation
rg -t ts -t tsx -A 10 "ReservationSheetContent"
# Also search for any state management related code
rg -t ts -t tsx "useState.*Reservation"
Length of output: 145
Script:
#!/bin/bash
# Search for ReservationSheetContent component implementation and state management
rg "ReservationSheetContent" -g "*.ts*" -A 10
# Look for any state management in the component
rg "useState.*Reservation" -g "*.ts*"
Length of output: 6723
Script:
#!/bin/bash
# Get the full content of ReservationSheetContent
cat apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx
Length of output: 4901
11-12: 인터페이스 타입 정의가 더 유연해졌습니다.
selectedSchedule과 selectedRoom이 옵셔널로 변경되어 컴포넌트의 재사용성이 향상되었습니다. 하지만 타입 안정성을 위해 다음 사항들을 확인해주세요:
selectedSchedule이 null일 때의 처리selectedRoom이 null일 때의 처리
다음 스크립트로 관련 처리 로직을 확인할 수 있습니다:
✅ Verification successful
타입 변경에 대한 안전성이 확인되었습니다.
ReservationSheetContent 컴포넌트에서 다음과 같이 null 처리가 적절히 구현되어 있습니다:
isEditMode조건에서selectedSchedule && selectedSchedule.user._id형태로 안전한 체크selectedSchedule?._id옵셔널 체이닝을 통한 null 안전성 확보ReservationForm으로 props 전달 시 타입 안전성 유지
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# ReservationSheetContent 컴포넌트에서 null 처리 확인
ast-grep --pattern 'selectedSchedule === null || selectedRoom === null'
# 조건부 렌더링 패턴 확인
ast-grep --pattern 'if (!selectedSchedule || !selectedRoom) {
$$$
}'
Length of output: 146
Script:
#!/bin/bash
# Find the file and check its content
cat apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx
# Search for usage of selectedSchedule and selectedRoom
rg "selectedSchedule|selectedRoom" apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx -A 2
# Look for any conditional rendering or null checks
ast-grep --pattern 'selectedSchedule ? $$ : $$'
ast-grep --pattern 'selectedRoom ? $$ : $$'
Length of output: 1891
Script:
#!/bin/bash
# Check ReservationSheetContent implementation for null handling
cat apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx
# Search for any conditional checks in ReservationSheetContent
rg "selectedSchedule|selectedRoom" apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx -A 2
Length of output: 5636
apps/web/app/meetings/_components/Reservation/ReservationSheets.tsx (2)
1-14: 타입 정의가 명확하고 잘 구성되어 있습니다!
인터페이스와 타입 정의가 명확하게 되어있으며, 공유 패키지와 로컬 타입을 적절히 활용하고 있습니다.
20-41: 반응형 디자인이 잘 구현되어 있습니다!
데스크톱과 모바일 뷰를 분리하여 처리한 방식이 깔끔하며, Tailwind CSS 클래스를 활용한 반응형 구현이 적절합니다. 컴포넌트 구조도 잘 분리되어 있습니다.
apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleItem.tsx (1)
5-5: 타입 변경에 따른 마이그레이션 확인 필요
Schedule에서 IReservation으로의 타입 변경이 이루어졌습니다. 이는 데이터 구조의 중요한 변경을 의미합니다.
Also applies to: 9-9
✅ Verification successful
타입 변경이 적절하게 적용되었습니다
Schedule 타입에서 IReservation 타입으로의 변경이 전체 코드베이스에서 일관되게 적용되어 있습니다. 검증 결과:
IReservation타입은@repo/types에서 정의되어 전체 프로젝트에서 공통으로 사용됩니다.- 모든 관련 컴포넌트(
ScheduleItem,ScheduleRow,ScheduleTable등)가IReservation타입을 일관되게 사용하고 있습니다. - API 호출과 데이터 흐름이 새로운 타입 구조와 잘 통합되어 있습니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Schedule 타입을 사용하는 다른 컴포넌트들 확인
# Schedule 타입 사용 검색
rg -l "Schedule" --type typescript
# IReservation 타입 사용 검색
rg -l "IReservation" --type typescript
Length of output: 143
Script:
#!/bin/bash
# Description: Schedule 타입과 IReservation 타입 사용 검색
# Schedule 타입 사용 검색
rg -l "Schedule" --type ts --type tsx
# IReservation 타입 사용 검색
rg -l "IReservation" --type ts --type tsx
# Schedule 타입 정의 검색
ast-grep --pattern 'type Schedule = $_'
ast-grep --pattern 'interface Schedule $_'
# IReservation 타입 정의 검색
ast-grep --pattern 'type IReservation = $_'
ast-grep --pattern 'interface IReservation $_'
Length of output: 317
Script:
#!/bin/bash
# Description: 타입 사용 및 정의 검색
# 파일 검색
rg -l "Schedule"
rg -l "IReservation"
# 타입 사용 컨텍스트 확인
rg "Schedule.*" -A 2 -B 2
rg "IReservation.*" -A 2 -B 2
# 파일 내용 확인
fd "ScheduleItem.tsx" --exec cat {}
Length of output: 70562
apps/web/app/_hooks/useSlotReservations.ts (1)
1-1: ESLint 비활성화 지시문 사용 검토 필요
ESLint의 @typescript-eslint/no-unsafe-assignment 규칙을 비활성화하는 것은 타입 안전성을 저해할 수 있습니다. 해당 규칙을 비활성화해야 하는 구체적인 이유를 검토하고, 가능하다면 타입 안전성을 유지하면서 문제를 해결하는 방법을 찾아보는 것이 좋겠습니다.
apps/web/app/meetings/_components/MeetingRoomSchedule.tsx (1)
13-17: 날짜 유효성 검증 추가 필요
selectedDate가 유효한 날짜인지 확인하는 검증 로직이 필요합니다. 잘못된 날짜 형식으로 인한 오류를 방지할 수 있습니다.
apps/web/app/types/ReservationFormTypes.ts (2)
15-17: 구조가 명확하고 적절합니다
NotesInputProps 인터페이스가 react-hook-form의 Control 타입을 잘 활용하고 있습니다.
1-5: CreateReservationRequest 타입 정의 확인 필요
CreateReservationRequest 타입이 예약 생성에 필요한 모든 필드를 포함하고 있는지 확인이 필요합니다.
✅ Verification successful
Let me verify if this type aligns with the IReservation interface to ensure consistency.
CreateReservationRequest 타입이 적절하게 정의되어 있음을 확인했습니다
CreateReservationRequest 타입이 IReservation 인터페이스와 일관성 있게 정의되어 있으며, 예약 생성에 필요한 모든 필수 필드를 포함하고 있습니다:
- 서버 측에서 자동 생성되는 필드(_id, createdAt, updatedAt)는 제외
- 필수 필드인 userId, itemType, startAt, endAt, status가 모두 포함
- 선택적 필드인 notes와 attendees도 적절히 포함
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# CreateReservationRequest 타입 정의 확인
ast-grep --pattern 'interface CreateReservationRequest {
$$$
}'
Length of output: 537
Script:
#!/bin/bash
# IReservation 인터페이스 정의 확인
ast-grep --pattern 'interface IReservation {
$$$
}'
Length of output: 949
apps/web/package.json (1)
17-17: date-fns 의존성 추가 검토
date-fns 라이브러리 추가는 회의실 예약 시스템의 시간 처리를 위해 적절한 선택입니다. 다만, 버전 호환성을 확인해주시기 바랍니다.
다음 스크립트를 실행하여 의존성 호환성을 확인하세요:
✅ Verification successful
date-fns 의존성 추가가 적절히 구현되었습니다
코드베이스 검토 결과, date-fns의 다음 기능들이 올바르게 사용되고 있습니다:
parseISO: ISO 문자열 파싱format,parse,differenceInMinutes,addMinutes: 시간 계산 및 포맷팅- 모든 사용 사례가 v4.1.0 버전과 호환됩니다
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: date-fns 버전 호환성 검증
# package-lock.json 또는 yarn.lock에서 date-fns 관련 의존성 확인
fd -e json -e lock . | xargs cat | grep -A 5 "date-fns"
# date-fns를 사용하는 파일 검색
rg -l "from ['|\"]date-fns"
Length of output: 386
Script:
#!/bin/bash
# Check the actual usage of date-fns in the files
echo "=== timeToMinutes.ts ==="
cat apps/web/app/utils/timeToMinutes.ts
echo -e "\n=== ReservationForm.tsx ==="
cat apps/web/app/meetings/_components/Reservation/ReservationForm.tsx
echo -e "\n=== validateTime.ts ==="
cat apps/web/app/utils/validateTime.ts
Length of output: 11452
apps/web/app/meetings/_components/Reservation/ReservationForm/RoomDropdown.tsx (1)
10-45: 전반적으로 잘 구현된 컴포넌트입니다
컴포넌트의 기본 구조와 기능이 잘 구현되어 있습니다. 제안된 개선사항들을 반영하면 더욱 완성도 높은 컴포넌트가 될 것 같습니다.
apps/web/app/_hooks/useCurrentTimePosition.ts (2)
3-12: 인터페이스 정의가 명확하고 완벽합니다!
타입 정의가 명확하며, currentPosition이 null이 될 수 있다는 것을 올바르게 표현하고 있습니다.
53-54: 반환값이 인터페이스와 일치하며 적절합니다!
타입 정의에 맞게 올바르게 구현되어 있습니다.
apps/web/api/items.ts (1)
1-8: 타입 정의와 임포트가 잘 구성되어 있습니다!
필요한 타입들과 상수들이 적절하게 임포트되어 있으며, GetAllItemsParams 인터페이스가 명확하게 정의되어 있습니다.
apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx (1)
3-14: 타입 정의가 잘 되어있습니다!
인터페이스와 타입 정의가 명확하게 되어있으며, 예약 기능에 필요한 모든 속성이 포함되어 있습니다.
apps/web/app/utils/validateTime.ts (1)
78-82: 시간 중복 검사 로직이 잘 구현되어 있습니다.
시작 시간과 종료 시간의 중복을 정확하게 검사하고 있습니다. 코드가 명확하고 이해하기 쉽습니다.
apps/web/app/meetings/_components/Reservation/ReservationForm/AttendeesMultiSelect.tsx (1)
9-14: 컴포넌트 구조 승인
컴포넌트의 props 구조와 타입 정의가 잘 되어있습니다. TypeScript를 효과적으로 활용하고 있습니다.
packages/ui/src/components/common/Dropdown/MultiSelectDropdown.tsx (1)
156-156: 인터페이스 확장이 적절하게 구현되었습니다.
className prop의 추가로 컴포넌트의 스타일링 유연성이 향상되었습니다. optional로 설정한 것이 기존 코드와의 호환성 측면에서 적절합니다.
apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx (1)
9-13: 인터페이스 정의가 개선되었습니다
ScheduleTableDesktopProps 인터페이스가 rooms, meetingsData, selectedDate를 포함하도록 확장되어 컴포넌트의 기능성을 향상시켰습니다.
apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx (3)
68-74: 'ReservationSheets' 컴포넌트에 올바른 props 전달
'ReservationSheets' 컴포넌트에 필요한 props가 정확하게 전달되고 있어 기능이 정상적으로 동작할 것으로 예상됩니다.
36-44: 'handleSlotClick' 함수의 시간 계산 로직 검증 필요
'handleSlotClick' 함수에서 시간 문자열을 생성하는 로직이 'startHour' 값이 변경되어도 올바르게 동작하는지 확인이 필요합니다. 특히 'startHour'가 0이 아닌 경우에도 예상한 결과를 출력하는지 테스트해주세요.
시간 계산 로직의 정확성을 검증하기 위해 다음 스크립트를 실행하세요:
✅ Verification successful
Let me analyze the code and generate additional verification scripts to check the time calculation logic more thoroughly.
This will help us verify:
- How startHour is initialized and if it can be changed
- If there are any tests validating the time calculation
- If there are any related time utility functions that might affect the calculation
시간 계산 로직이 정확하게 구현되어 있음을 확인했습니다
시간 계산 로직을 검증한 결과:
startHour는 0으로 하드코딩되어 있어 변경될 우려가 없습니다- 시간 계산은
timeToMinutes유틸리티 함수를 사용하여 정확하게 처리됩니다 handleSlotClick함수의 시간 계산 로직이 올바르게 구현되어 있습니다:clickedTimeMinutes계산이 정확합니다 (시작 시간 + 슬롯 인덱스 * 슬롯 당 시간)- 시간과 분 변환이 올바르게 이루어집니다
- 2자리 숫자 포맷팅이 제대로 적용됩니다
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: 'handleSlotClick' 함수의 시간 계산 로직을 확인합니다.
# 'handleSlotClick' 함수의 구현부 출력
rg 'const handleSlotClick' -A 15
# 'startHour' 값의 사용처 확인
rg 'startHour' -A 5
Length of output: 10076
Script:
#!/bin/bash
# Check if startHour is used as a prop or if it's always hardcoded
rg -B 5 'const startHour' apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx
# Check if there are any tests for this component
fd -e test.tsx -e spec.tsx ScheduleRow
# Check for any other time-related utility functions that might be used
rg 'timeToMinutes|formatTime|parseTime' -A 5
Length of output: 3719
13-17: 'ScheduleRowProps' 인터페이스 변경으로 인한 영향 확인 필요
'ScheduleRowProps' 인터페이스의 프로퍼티 타입과 함수 시그니처가 변경되었습니다. 이로 인해 'ScheduleRow' 컴포넌트를 사용하는 모든 곳에서 타입 오류가 발생할 수 있으므로, 해당 변경사항이 반영되었는지 확인해야 합니다.
아래 스크립트를 실행하여 'ScheduleRow' 컴포넌트의 사용처를 찾아 변경사항이 올바르게 적용되었는지 확인하세요:
✅ Verification successful
Let me check the implementation of the onSlotClick prop since it's part of the interface changes.
Let me check the implementation of the ScheduleRow component and its usage of onSlotClick.
ScheduleRow 컴포넌트의 인터페이스 변경이 올바르게 처리되었습니다
코드베이스 분석 결과, ScheduleRow 컴포넌트의 인터페이스 변경이 모든 사용처에서 올바르게 처리되고 있음을 확인했습니다:
ScheduleTableMobile.tsx와ScheduleTableDesktop.tsx모두 필수 props인schedules,room,slotWidth,slotHeight를 올바르게 전달하고 있습니다.onSlotClickprop은 선택적(optional)이며, 현재 구현에서는 내부적으로handleSlotClick함수를 통해 사이드바 상태를 관리하고 있어 외부에서 전달받지 않아도 정상적으로 동작합니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: 'ScheduleRow' 컴포넌트의 사용처를 검색하여 인터페이스 변경에 따른 수정이 이루어졌는지 확인합니다.
# 'ScheduleRow'를 임포트하는 모든 파일 찾기
rg 'import.*ScheduleRow' -l
# 'ScheduleRow' 컴포넌트를 렌더링하는 코드 확인
rg '<ScheduleRow' -A 5
Length of output: 1656
Script:
#!/bin/bash
# Check the implementation and usage of onSlotClick in ScheduleTable components
rg 'onSlotClick' -A 5 apps/web/app/meetings/_components/Schedule/ScheduleTable/
Length of output: 79
Script:
#!/bin/bash
# Check the implementation of ScheduleRow component
cat apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx
# Also check for any onSlotClick usage in the Schedule directory
rg 'onSlotClick' apps/web/app/meetings/_components/Schedule/
Length of output: 3579
apps/web/app/meetings/_components/Reservation/ReservationForm.tsx (3)
71-75: defaultValues 초기화 시 타입 불일치 확인 필요
defaultValues를 설정할 때 CreateReservationRequest 타입과 일치하는지 확인해야 합니다. 특히 startAt, endAt 등의 날짜 형식이 적절하게 초기화되었는지 검토하고, 타입스크립트에서 오류가 발생하지 않도록 유의해야 합니다.
262-263: 버튼 활성화 조건 로직 확인 필요
Button 컴포넌트의 isActive 속성에 isValid ? attendeesSelected : undefined를 전달하고 있습니다. 이는 isValid가 true이고 attendeesSelected가 true일 때만 버튼이 활성화됩니다. 의도한 동작인지 확인하고, 사용자 입장에서 예약자 선택 없이 예약이 불가능한지 고려해야 합니다.
183-184: 자기 자신의 예약 겹침 확인 로직 검토 필요
예약 수정 시 자기 자신의 예약이 겹침 검사에서 올바르게 제외되는지 확인이 필요합니다. 현재 조건문이 정확하게 작동하여 불필요한 에러가 발생하지 않도록 로직을 검토해야 합니다.
✅ Verification successful
Let me gather more context about the reservation overlap checking logic.
Let me gather one more piece of information about the overlap checking implementation.
예약 겹침 검사 로직이 올바르게 구현되어 있음을 확인했습니다
현재 구현된 로직에서는 다음과 같이 자기 자신의 예약을 올바르게 제외하고 있습니다:
- 수정 중인 예약(
selectedSchedule)과 동일한 ID를 가진 예약은 겹침 검사에서 제외됨 - 동일한 회의실에 대한 예약만 검사하여 불필요한 겹침 검사를 방지함
- 시간 겹침 로직도 정확하게 구현되어 있음
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: 자기 자신의 예약이 겹침 검사에서 제외되는지 확인
# Test: 예약 ID 비교 로직 확인
rg -A 5 'if \(selectedSchedule && reservation\._id === selectedSchedule\._id\)' apps/web/app/meetings/_components/Reservation/ReservationForm.tsx
Length of output: 366
Script:
#!/bin/bash
# Get the full overlap checking function implementation
ast-grep --pattern 'function checkOverlap($_) {
$$$
}'
# Get the full context of how selectedSchedule is used
rg -B 5 -A 10 "selectedSchedule" apps/web/app/meetings/_components/Reservation/ReservationForm.tsx
Length of output: 3552
Script:
#!/bin/bash
# Get the full context of overlap checking logic
rg -B 10 -A 10 "if \(selectedSchedule && reservation\._id === selectedSchedule\._id\)" apps/web/app/meetings/_components/Reservation/ReservationForm.tsx
Length of output: 697
| export const timeToMinutes = (time: Date | string): number => { | ||
| const date = typeof time === "string" ? parseISO(time) : time; | ||
| const hours = date.getHours(); | ||
| const minutes = date.getMinutes(); | ||
| return hours * 60 + minutes; | ||
| }; |
There was a problem hiding this comment.
💡 Codebase verification
🛠️ Refactor suggestion
코드베이스를 분석한 결과, 최종 응답을 생성할 수 있습니다.
시간대 처리는 이미 적절하게 구현되어 있으므로 변경이 불필요합니다.
현재 구현은 다음과 같은 이유로 안전합니다:
- 프론트엔드에서
timeToMinutes함수는 단순히 시간을 분으로 변환하는 유틸리티로 사용되며, 실제 예약 시간 처리는 백엔드에서 수행됩니다. - 예약 생성/수정 시 백엔드에서 다음과 같은 검증을 수행합니다:
- 시간은 10분 단위로만 설정 가능 (
isMinuteValid함수) - 시작 시간은 종료 시간보다 이전이어야 함
- 중복 예약 검사 수행
- 시간은 10분 단위로만 설정 가능 (
- 프론트엔드의 날짜/시간 표시는
Asia/Seoul시간대로 일관되게 처리됩니다.
다만, 입력값 유효성 검사는 개선이 필요합니다:
export const timeToMinutes = (time: Date | string): number => {
+ if (!time) {
+ throw new Error('시간값이 필요합니다.');
+ }
+
const date = typeof time === "string" ? parseISO(time) : time;
+
+ if (!(date instanceof Date) || isNaN(date.getTime())) {
+ throw new Error('유효하지 않은 날짜/시간 형식입니다.');
+ }
+
const hours = date.getHours();
const minutes = date.getMinutes();
return hours * 60 + minutes;
};🔗 Analysis chain
입력값 유효성 검사와 시간대 처리를 개선해주세요.
현재 구현에는 다음과 같은 잠재적인 문제가 있습니다:
- 잘못된 ISO 문자열에 대한 예외 처리가 없습니다
- 시간대(timezone) 처리가 명시적이지 않습니다
다음과 같이 개선하는 것을 제안합니다:
export const timeToMinutes = (time: Date | string): number => {
- const date = typeof time === "string" ? parseISO(time) : time;
+ try {
+ const date = typeof time === "string" ? parseISO(time) : time;
+
+ if (!(date instanceof Date) || isNaN(date.getTime())) {
+ throw new Error('유효하지 않은 날짜/시간 형식입니다.');
+ }
+
+ // UTC 기준으로 시간 계산
+ const hours = date.getUTCHours();
+ const minutes = date.getUTCMinutes();
+ return hours * 60 + minutes;
+ } catch (error) {
+ throw new Error('시간 변환 중 오류가 발생했습니다: ' + error.message);
+ }
- const hours = date.getHours();
- const minutes = date.getMinutes();
- return hours * 60 + minutes;
};시간대 처리가 올바르게 되는지 확인하기 위해 다음 스크립트를 실행해보세요:
Let me gather more information about how the dates are handled in the codebase, particularly around the schedules.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: 프로젝트에서 timeToMinutes 함수의 사용처를 검색하여 시간대 관련 이슈가 있는지 확인
# timeToMinutes 함수 사용 검색
echo "Searching for timeToMinutes usage..."
rg "timeToMinutes" -A 5 -B 5
Length of output: 2586
Script:
#!/bin/bash
# Check how startAt and endAt are handled in the codebase
echo "Searching for schedule date handling..."
rg "startAt|endAt" -A 5 -B 5
# Check for any timezone-related configurations or utilities
echo -e "\nSearching for timezone-related code..."
rg "timezone|getUTC|setUTC|toUTC" -A 5 -B 5
Length of output: 56325
| function RoomNameSkeleton(): JSX.Element { | ||
| return ( | ||
| <div className="transition-linear md:min-w-128 rounded-8 border-1 text-lg-medium ml-16 inline-block h-48 min-w-80 animate-pulse gap-8 border-gray-200/10 bg-white p-12 text-center md:h-60 md:px-32 md:py-16"> | ||
| <div className="rounded-4 h-full w-full bg-gray-400" /> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
스켈레톤 UI의 접근성 개선이 필요합니다
스켈레톤 UI가 구현되어 있지만, 스크린 리더 사용자를 위한 접근성 개선이 필요합니다.
다음과 같이 aria 속성을 추가하는 것을 제안드립니다:
function RoomNameSkeleton(): JSX.Element {
return (
- <div className="transition-linear md:min-w-128 rounded-8 border-1 text-lg-medium ml-16 inline-block h-48 min-w-80 animate-pulse gap-8 border-gray-200/10 bg-white p-12 text-center md:h-60 md:px-32 md:py-16">
+ <div
+ role="status"
+ aria-label="회의실 이름 로딩 중"
+ className="transition-linear md:min-w-128 rounded-8 border-1 text-lg-medium ml-16 inline-block h-48 min-w-80 animate-pulse gap-8 border-gray-200/10 bg-white p-12 text-center md:h-60 md:px-32 md:py-16"
+ >
<div className="rounded-4 h-full w-full bg-gray-400" />
+ <span className="sr-only">로딩 중...</span>
</div>
);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function RoomNameSkeleton(): JSX.Element { | |
| return ( | |
| <div className="transition-linear md:min-w-128 rounded-8 border-1 text-lg-medium ml-16 inline-block h-48 min-w-80 animate-pulse gap-8 border-gray-200/10 bg-white p-12 text-center md:h-60 md:px-32 md:py-16"> | |
| <div className="rounded-4 h-full w-full bg-gray-400" /> | |
| </div> | |
| ); | |
| } | |
| function RoomNameSkeleton(): JSX.Element { | |
| return ( | |
| <div | |
| role="status" | |
| aria-label="회의실 이름 로딩 중" | |
| className="transition-linear md:min-w-128 rounded-8 border-1 text-lg-medium ml-16 inline-block h-48 min-w-80 animate-pulse gap-8 border-gray-200/10 bg-white p-12 text-center md:h-60 md:px-32 md:py-16" | |
| > | |
| <div className="rounded-4 h-full w-full bg-gray-400" /> | |
| <span className="sr-only">로딩 중...</span> | |
| </div> | |
| ); | |
| } |
| <div className="flex w-full"> | ||
| {Array.from({ length: 24 }, (_, index) => ( | ||
| <div key={`skeleton-${index}`} className="min-w-144 -ml-40 mt-6 flex animate-pulse items-center justify-center"> | ||
| <div className="h-12 w-40 rounded bg-gray-400" /> | ||
| </div> | ||
| ))} | ||
| </div> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
접근성 개선 제안
스켈레톤 UI에 대한 접근성 지원이 필요합니다.
return (
- <div className="flex w-full">
+ <div
+ className="flex w-full"
+ role="progressbar"
+ aria-label="시간 로딩 중"
+ aria-busy="true"
+ >📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className="flex w-full"> | |
| {Array.from({ length: 24 }, (_, index) => ( | |
| <div key={`skeleton-${index}`} className="min-w-144 -ml-40 mt-6 flex animate-pulse items-center justify-center"> | |
| <div className="h-12 w-40 rounded bg-gray-400" /> | |
| </div> | |
| ))} | |
| </div> | |
| <div | |
| className="flex w-full" | |
| role="progressbar" | |
| aria-label="시간 로딩 중" | |
| aria-busy="true" | |
| > | |
| {Array.from({ length: 24 }, (_, index) => ( | |
| <div key={`skeleton-${index}`} className="min-w-144 -ml-40 mt-6 flex animate-pulse items-center justify-center"> | |
| <div className="h-12 w-40 rounded bg-gray-400" /> | |
| </div> | |
| ))} | |
| </div> |
apps/web/api/users.ts
Outdated
| export const getAllUser = async (): Promise<IUser[]> => { | ||
| const { data } = await axiosRequester<IUser[]>({ | ||
| options: { | ||
| method: "GET", | ||
| url: API_ENDPOINTS.USERS.GET_ALL, | ||
| }, | ||
| }); | ||
|
|
||
| return data; | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
에러 처리 로직 추가가 필요합니다
API 호출 실패 시 적절한 에러 처리가 없습니다. 사용자에게 의미 있는 에러 메시지를 제공하기 위해 try-catch 블록을 추가하는 것이 좋겠습니다.
다음과 같이 수정을 제안드립니다:
export const getAllUser = async (): Promise<IUser[]> => {
+ try {
const { data } = await axiosRequester<IUser[]>({
options: {
method: "GET",
url: API_ENDPOINTS.USERS.GET_ALL,
},
});
return data;
+ } catch (error) {
+ throw new Error('사용자 목록을 불러오는데 실패했습니다.');
+ }
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const getAllUser = async (): Promise<IUser[]> => { | |
| const { data } = await axiosRequester<IUser[]>({ | |
| options: { | |
| method: "GET", | |
| url: API_ENDPOINTS.USERS.GET_ALL, | |
| }, | |
| }); | |
| return data; | |
| }; | |
| export const getAllUser = async (): Promise<IUser[]> => { | |
| try { | |
| const { data } = await axiosRequester<IUser[]>({ | |
| options: { | |
| method: "GET", | |
| url: API_ENDPOINTS.USERS.GET_ALL, | |
| }, | |
| }); | |
| return data; | |
| } catch (error) { | |
| throw new Error('사용자 목록을 불러오는데 실패했습니다.'); | |
| } | |
| }; |
apps/web/api/users.ts
Outdated
| const { data } = await axiosRequester<IUser[]>({ | ||
| options: { | ||
| method: "GET", | ||
| url: API_ENDPOINTS.USERS.GET_ALL, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
💡 Codebase verification
API 응답 데이터 검증이 필요합니다
현재 axiosRequester는 제네릭 타입을 사용하여 타입 힌트만 제공할 뿐, 실제 런타임에서 서버 응답 데이터가 IUser[] 타입과 일치하는지 검증하지 않습니다. 다음과 같은 방법으로 개선할 수 있습니다:
- Zod나 Yup과 같은 런타임 타입 검증 라이브러리를 도입하여 응답 데이터 유효성 검사
- 커스텀 타입 가드를 작성하여 응답 데이터 검증
- API 응답을 처리하기 전에 필수 필드 존재 여부 확인
🔗 Analysis chain
API 응답 타입 검증이 필요합니다
서버로부터 받은 데이터가 예상한 형식(IUser[])과 일치하는지 검증하는 로직이 없습니다. 런타임 에러를 방지하기 위해 타입 검증을 추가하는 것이 좋겠습니다.
API 응답 타입을 확인하기 위해 다음 스크립트를 실행해보겠습니다:
Let me check if there's any runtime type validation logic in place.
Let me check if there's any type validation in the axiosRequester utility.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# IUser 인터페이스 정의를 찾아서 응답 타입과 일치하는지 확인
ast-grep --pattern 'interface IUser {
$$$
}'
Length of output: 665
Script:
#!/bin/bash
# Check for any validation utilities or type checking logic
rg -g '!*.test.*' -g '!*.spec.*' "zod|yup|joi|validator|validate|schema" apps/web/api/
Length of output: 88
Script:
#!/bin/bash
# Check axiosRequester implementation
rg -g '!*.test.*' -g '!*.spec.*' "axiosRequester" --type ts -A 10
Length of output: 14369
| export const deleteReservation = async (reservationId: string): Promise<void> => { | ||
| await axiosRequester({ | ||
| options: { | ||
| method: "DELETE", | ||
| url: API_ENDPOINTS.RESERVATION.DELETE_RESERVATION(reservationId), | ||
| }, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
deleteReservation 함수의 에러 핸들링 필요
deleteReservation 함수에서 API 호출 실패 시 에러 처리가 없습니다. 요청 실패 시 적절한 대응을 위해 에러 핸들링을 추가하는 것이 좋습니다.
에러 처리를 추가하려면 다음과 같이 수정할 수 있습니다:
export const deleteReservation = async (reservationId: string): Promise<void> => {
- await axiosRequester({
- options: {
- method: "DELETE",
- url: API_ENDPOINTS.RESERVATION.DELETE_RESERVATION(reservationId),
- },
- });
+ try {
+ await axiosRequester({
+ options: {
+ method: "DELETE",
+ url: API_ENDPOINTS.RESERVATION.DELETE_RESERVATION(reservationId),
+ },
+ });
+ } catch (error) {
+ // 에러 처리 로직 추가
+ throw error;
+ }
};Committable suggestion skipped: line range outside the PR's diff.
| export interface CreateReservationRequest { | ||
| userId: string; | ||
| itemType: "room"; | ||
| startAt: string; | ||
| endAt: string; | ||
| status: "reserved"; | ||
| notes: string; | ||
| attendees: string[]; | ||
| } |
There was a problem hiding this comment.
itemType과 status의 하드코딩된 값 검토 필요
CreateReservationRequest 인터페이스에서 itemType이 "room"으로, status가 "reserved"로 고정되어 있습니다. 예약 생성 시 다른 아이템 타입이나 상태를 지원해야 한다면 해당 필드를 유연하게 변경하는 것이 좋습니다.
다음과 같이 수정할 것을 제안합니다:
export interface CreateReservationRequest {
userId: string;
- itemType: "room";
+ itemType: "room" | "seat" | "equipment";
startAt: string;
endAt: string;
- status: "reserved";
+ status: TReservationStatus;
notes: string;
attendees: string[];
}Committable suggestion skipped: line range outside the PR's diff.
| export const updateReservation = async ( | ||
| reservationId: string, | ||
| reservationData: UpdateReservationRequest, | ||
| ): Promise<IReservation> => { | ||
| const { data } = await axiosRequester<UpdateReservationResponse>({ | ||
| options: { | ||
| method: "PATCH", | ||
| url: API_ENDPOINTS.RESERVATION.UPDATE_RESERVATION(reservationId), | ||
| data: reservationData, | ||
| }, | ||
| }); | ||
|
|
||
| return data.updatedReservation; | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
updateReservation 함수의 에러 핸들링 필요
updateReservation 함수에서도 API 요청 실패 시 에러 처리가 없습니다. 예외 상황에 대비하여 에러 핸들링을 추가하는 것이 바람직합니다.
다음과 같이 수정할 수 있습니다:
export const updateReservation = async (
reservationId: string,
reservationData: UpdateReservationRequest,
): Promise<IReservation> => {
- const { data } = await axiosRequester<UpdateReservationResponse>({
- options: {
- method: "PATCH",
- url: API_ENDPOINTS.RESERVATION.UPDATE_RESERVATION(reservationId),
- data: reservationData,
- },
- });
-
- return data.updatedReservation;
+ try {
+ const { data } = await axiosRequester<UpdateReservationResponse>({
+ options: {
+ method: "PATCH",
+ url: API_ENDPOINTS.RESERVATION.UPDATE_RESERVATION(reservationId),
+ data: reservationData,
+ },
+ });
+
+ return data.updatedReservation;
+ } catch (error) {
+ // 에러 처리 로직 추가
+ throw error;
+ }
};Committable suggestion skipped: line range outside the PR's diff.
| const { data } = await axiosRequester<CreateReservationResponse>({ | ||
| options: { | ||
| method: "POST", | ||
| url: API_ENDPOINTS.RESERVATION.CREATE_RESERVATION(itemId), | ||
| data: reservationData, | ||
| }, | ||
| }); | ||
|
|
||
| return data.savedReservation; | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
에러 핸들링 추가 필요
createReservation 함수에서 API 호출 실패 시 에러에 대한 처리가 이루어지지 않고 있습니다. 안정적인 서비스 제공을 위해 예외 상황에 대한 처리를 추가하는 것이 좋습니다.
다음과 같이 코드에 에러 핸들링을 추가할 수 있습니다:
export const createReservation = async (
itemId: string,
reservationData: CreateReservationRequest,
): Promise<IReservation> => {
- const { data } = await axiosRequester<CreateReservationResponse>({
- options: {
- method: "POST",
- url: API_ENDPOINTS.RESERVATION.CREATE_RESERVATION(itemId),
- data: reservationData,
- },
- });
-
- return data.savedReservation;
+ try {
+ const { data } = await axiosRequester<CreateReservationResponse>({
+ options: {
+ method: "POST",
+ url: API_ENDPOINTS.RESERVATION.CREATE_RESERVATION(itemId),
+ data: reservationData,
+ },
+ });
+
+ return data.savedReservation;
+ } catch (error) {
+ // 에러 처리 로직 추가
+ throw error;
+ }
};Committable suggestion skipped: line range outside the PR's diff.
| export function ReservationForm({ | ||
| onSubmit, | ||
| selectedTime, | ||
| resetTrigger, | ||
| selectedRoom, | ||
| selectedSchedule, | ||
| onDelete, | ||
| }: ReservationFormProps): JSX.Element { |
There was a problem hiding this comment.
ReservationFormProps 타입 정의 확인 필요
현재 ReservationForm 컴포넌트에서 사용되는 selectedRoom, selectedSchedule, onDelete 등이 ReservationFormProps 인터페이스에 포함되어 있습니다. 그러나 해당 props들이 선택적(optional)인지 필수인지 명확하지 않습니다. 타입 정의에서 누락되었거나 잘못된 부분이 있는지 확인하고, 필요에 따라 ?를 추가하여 선택적 props로 지정해야 합니다.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Outside diff range and nitpick comments (4)
apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx (2)
25-32: 모바일 대응이 잘 구현되었으나, 접근성 개선이 필요합니다.현재 시간 표시기의 반응형 디자인이 잘 구현되었습니다. 하지만 스크린 리더 사용자를 위한 접근성 속성이 누락되었습니다.
다음과 같이 aria 속성을 추가하는 것을 제안드립니다:
- <div className="relative"> + <div className="relative" role="timer" aria-label="현재 시간 표시"> <div className="border-custom-black md:h-120 absolute z-50 ml-36 h-80 border-l-2 border-solid" style={{ left: `${currentPosition}px` }} + aria-hidden="true" />
27-28: 스타일 관련 개선 제안현재 인라인 스타일과 클래스네임을 혼용하고 있습니다. 일관성을 위해 Tailwind 클래스로 통일하는 것이 좋을 것 같습니다.
다음과 같이 수정하는 것을 제안드립니다:
- className="border-custom-black md:h-120 absolute z-50 ml-36 h-80 border-l-2 border-solid" - style={{ left: `${currentPosition}px` }} + className={`border-custom-black md:h-120 absolute z-50 ml-36 h-80 border-l-2 border-solid left-[${currentPosition}px]`}apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx (2)
23-24: 상태 관리 최적화 제안선택된 시간과 스케줄 상태를 하나의 객체로 통합하여 관리하는 것을 고려해보세요:
-const [selectedTime, setSelectedTime] = useState<string | null>(null); -const [selectedSchedule, setSelectedSchedule] = useState<IReservation | null>(null); +const [selection, setSelection] = useState<{ + time: string | null; + schedule: IReservation | null; +}>({ time: null, schedule: null });
30-34: 상수 값을 분리하여 관리하는 것을 추천드립니다.시간 관련 설정값들을 상수로 분리하면 유지보수가 더 용이해질 것 같습니다:
+const TIME_CONSTANTS = { + START_HOUR: 0, + END_HOUR: 24, + MINUTES_PER_SLOT: 30, +} as const; + -const startHour = 0; -const endHour = 24; -const minutesPerSlot = 30; +const { START_HOUR: startHour, END_HOUR: endHour, MINUTES_PER_SLOT: minutesPerSlot } = TIME_CONSTANTS;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx(2 hunks)apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx(1 hunks)
🔇 Additional comments (3)
apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx (1)
14-18: 커스텀 훅 구현이 잘 되었습니다!
커스텀 훅을 사용하여 시간 계산 로직을 분리한 것이 코드의 가독성과 재사용성을 크게 향상시켰습니다.
apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx (2)
4-7: 타입 정의가 적절히 업데이트되었습니다.
IReservation 타입과 SelectedRoom 타입을 사용하여 예약 시스템에 맞게 잘 수정되었습니다.
Also applies to: 13-17
56-74: 컴포넌트 구조가 잘 설계되었습니다.
ScheduleSlots와 ReservationSheets 컴포넌트를 활용한 구조가 깔끔하고, props 전달이 명확합니다. CurrentTimeIndicator의 통합도 잘 되어있습니다.
| const handleSlotClick = (index: number, schedule?: IReservation): void => { | ||
| const clickedTimeMinutes = startHour * 60 + index * minutesPerSlot; | ||
| const hours = Math.floor(clickedTimeMinutes / 60); | ||
| const minutes = clickedTimeMinutes % 60; | ||
| const timeString = `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`; | ||
|
|
||
| setSelectedTime(timeString); | ||
| setSelectedSchedule(schedule ?? null); | ||
| setSelectedRoom(room); | ||
| openSidebar(); | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
시간 변환 로직 개선이 필요합니다.
시간 계산 로직을 별도의 유틸리티 함수로 분리하고, 유효하지 않은 시간에 대한 처리를 추가하는 것이 좋을 것 같습니다:
const convertIndexToTime = (index: number, startHour: number, minutesPerSlot: number): string => {
const clickedTimeMinutes = startHour * 60 + index * minutesPerSlot;
if (clickedTimeMinutes < 0 || clickedTimeMinutes >= 24 * 60) {
throw new Error('Invalid time index');
}
const hours = Math.floor(clickedTimeMinutes / 60);
const minutes = clickedTimeMinutes % 60;
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
};There was a problem hiding this comment.
엇 이거 members.ts 파일에 getMembers 사용하면 될거야
| interface UseCurrentTimePositionProps { | ||
| slotWidth: number; | ||
| startHour: number; | ||
| endHour: number; | ||
| } |
| interface UseCurrentTimePositionReturn { | ||
| currentPosition: number | null; | ||
| currentTime: string; | ||
| } |
There was a problem hiding this comment.
return type 같은데 return 보다는 result로 이름짓는건 어떨까?
| export enum ModalType { | ||
| CREATE_UPDATE = "create/update", | ||
| DELETE = "delete", | ||
| } |
There was a problem hiding this comment.
책한권만 읽고 무조건적으로 안좋다는건 아니고 이런글도 있으니까 참고해봤으면 좋겠어!
| export const QUERY_KEYS = { | ||
| meetings: (date: string, type: string) => ["meetings", date, type] as const, | ||
| }; |
There was a problem hiding this comment.
querykey는 혜린이꺼 pr merge되면 한 파일로 이동시키는게 좋을거같아~
There was a problem hiding this comment.
merge 했어! 확인 부탁합니다~~
| {isLoading ? ( | ||
| <div>회의실 로딩 중...</div> | ||
| ) : isError ? ( |
There was a problem hiding this comment.
| {isLoading ? ( | |
| <div>회의실 로딩 중...</div> | |
| ) : isError ? ( | |
| {isLoading ? ( | |
| <div>회의실 로딩 중...</div> | |
| ) : isError ? ( |
이런 로딩 피드백이 정말 알맞는 UX일까 싶어
| onSuccess: async () => { | ||
| notify({ | ||
| type: "success", | ||
| message: isEditMode ? NOTIFICATION_MESSAGES.update : NOTIFICATION_MESSAGES.create, | ||
| }); | ||
| await queryClient.invalidateQueries({ | ||
| queryKey: QUERY_KEYS.meetings(formattedDate, MEETING_ROOMS_TYPE), | ||
| }); | ||
| setIsModalOpen(false); | ||
| onClose(); | ||
| }, |
There was a problem hiding this comment.
| onSuccess: async () => { | |
| notify({ | |
| type: "success", | |
| message: isEditMode ? NOTIFICATION_MESSAGES.update : NOTIFICATION_MESSAGES.create, | |
| }); | |
| await queryClient.invalidateQueries({ | |
| queryKey: QUERY_KEYS.meetings(formattedDate, MEETING_ROOMS_TYPE), | |
| }); | |
| setIsModalOpen(false); | |
| onClose(); | |
| }, | |
| onSuccess: () => { | |
| notify({ | |
| type: "success", | |
| message: isEditMode ? NOTIFICATION_MESSAGES.update : NOTIFICATION_MESSAGES.create, | |
| }); | |
| void queryClient.invalidateQueries({ | |
| queryKey: QUERY_KEYS.meetings(formattedDate, MEETING_ROOMS_TYPE), | |
| }); | |
| setIsModalOpen(false); | |
| onClose(); | |
| }, |
비동기 처리는 필요 없을수도...?
There was a problem hiding this comment.
나 이런거 보면 내가 비동기에 대해 이해를 좀 잘못 하고 있는 거 같기두 함...
| onConfirm={() => { | ||
| if (modalType === ModalType.CREATE_UPDATE) { | ||
| if (formData) { | ||
| createOrUpdateReservation.mutate(formData); | ||
| } | ||
| } else if (modalType === ModalType.DELETE) { | ||
| confirmDelete(); | ||
| } | ||
| setIsModalOpen(false); | ||
| onClose(); | ||
| }} |
There was a problem hiding this comment.
이정도면 handler로 구성해서 관리하는게 나을수도 있을거같아~
| children: ReactNode; | ||
| } | ||
|
|
||
| export default function RoomName(props: RoomNameProps): JSX.Element { |
There was a problem hiding this comment.
| export default function RoomName(props: RoomNameProps): JSX.Element { | |
| export default function RoomName({children}:PropsWithChildren): JSX.Element { |
소소하지만 children만 받는경우에 react의 헬퍼타입을 사용해도 좋았던거같아~
There was a problem hiding this comment.
헉 그렇네 맞다 나 왜 까먹고있었지...
| const startHour = 0; | ||
| const endHour = 24; | ||
| const minutesPerSlot = 30; | ||
|
|
There was a problem hiding this comment.
매직넘버로 많이 사용되는 값인거같은데 상수화해서 한곳에서 관리하는건어때?
| export const QUERY_KEYS = { | ||
| meetings: (date: string, type: string) => ["meetings", date, type] as const, | ||
| }; |
There was a problem hiding this comment.
merge 했어! 확인 부탁합니다~~
| field.onChange(value); | ||
| clearErrors("endAt"); | ||
| trigger("endAt"); | ||
| }} |
There was a problem hiding this comment.
가독성을 위해 핸들러로 빼는 건 어때?! 따로 함수로 분리하는게 테스트코드 작성할 때도 용이한 걸로 알아~!
| style={{ width: slotWidth, height: slotHeight }} | ||
| className={`hover:bg-gray-60 relative cursor-pointer ${isClicked ? "bg-purple-100" : ""} ${ | ||
| isUserReservation ? "hover:bg-purple-100" : "" | ||
| }`} |
There was a problem hiding this comment.
className={cn(
"hover:bg-gray-60 relative cursor-pointer",
isClicked && "bg-purple-100",
isUserReservation && "hover:bg-purple-100"
)}
cn을 사용하는건 어때?!
|
|
||
| export default function ScheduleTooltip(props: ScheduleTooltipProps): JSX.Element { | ||
| const { title } = props; | ||
| const { children } = props; |
There was a problem hiding this comment.
위에서 보경이가 말한 것 처럼 children을 props로 뽑지 않고 바로 넘겨주는게 좋을 것 같아~~
🚀 작업 내용
회의실 예약 API 연동 진행 및 오류 전체 수정 및 create 전송 및 토스트 할당 및............................................
📝 참고 사항
🖼️ 스크린샷
🚨 관련 이슈 (이슈 번호)
✅ 체크리스트
중요 사항
Summary by CodeRabbit
릴리즈 노트
새로운 기능
버그 수정
성능 개선
문서화